Skip to content

feat(login): Persist non-default --server to the config file - #363

Closed
lluiscampos wants to merge 1 commit into
mendersoftware:masterfrom
lluiscampos:feat/login-save-server
Closed

feat(login): Persist non-default --server to the config file#363
lluiscampos wants to merge 1 commit into
mendersoftware:masterfrom
lluiscampos:feat/login-save-server

Conversation

@lluiscampos

Copy link
Copy Markdown

After a successful login with an explicit --server pointing to something different than hosted.mender.io, create or update .mender-clirc for further commands to use.

@lluiscampos
lluiscampos requested a review from a team as a code owner July 1, 2026 10:31
@lluiscampos

lluiscampos commented Jul 1, 2026

Copy link
Copy Markdown
Author

Hello bakend server team 👋

I am using eu.hosted.io for my PoC and this feature helps me greatly to not end up having annoying 401 because I forgot to specify the server url.

From a user point of view, if I do "mender-cli login ..." and the valid token is saved in disk, it would be nice that the info about where the token is valid (server url) is also saved in disk.

@mzedel

mzedel commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Hello @lluiscampos and what a nice surprise to see this!
Although I'm not part of the backend team I had a related idea - as all I use mender-artifact for is directly tied to an upload... If your usage is similar maybe the chances increase to get the mender-artifact extension through - independent of this I'll ping some people internally...

After a successful login with an explicit --server pointing to something
different than hosted.mender.io, create or update .mender-clirc for
further commands to use.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Lluis Miquel Martinez Campos <lluiscampos@proton.me>

@mzedel mzedel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks you! I'll leave the final verdict to someone else, but for CI to pass we would require you to: Make sure that the Author of the commit matches the one in Signed-off-by 😉

Comment thread cmd/login_config.go Outdated
@lluiscampos
lluiscampos force-pushed the feat/login-save-server branch from 0c055f0 to 5b9e0b2 Compare July 1, 2026 13:08
@lluiscampos

Copy link
Copy Markdown
Author

Heyho @mzedel as representative of the server team 😉

I fixed the linting issue and the signed-off-by 👍

(...) I had a related idea - as all I use mender-artifact for is directly tied to an upload... If your usage is similar maybe the chances increase to get the mender-artifact extension through

Do you mean an extension for mender-artifact tool to do server operations like upload? I don't understand your sentence. So far we you have kept them separated: mender-cli operates with the server (inc credentials for example) while mender-artifact deals with packaging, validating, reading, modifying etc of only local files (although sometimes uses 3rd party cloud services for signing).

@mzedel

mzedel commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Do you mean an extension for mender-artifact tool to do server operations like upload?

Exactly that! For me the artifact handling is very much aligned with e.g. docker and docker images - you can build, modify and extract them, but also move them around (IMO the push to the registry/ a Mender server is not much else). mender-cli OTOH handles primarily the device interactions and so far other API interactions are not extensive.
So my proposed idea is a slight rephrasing of mender-artifact to take care of the key artifact related aspects, and mender-cli key device related aspects or API in general.

@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.11940% with 16 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cmd/login_config.go 71.42% 11 Missing and 5 partials ⚠️

📢 Thoughts on this report? Let us know!

@lluiscampos

Copy link
Copy Markdown
Author

Exactly that!

I see. It is true that from a user point of view it makes sense (my same CI job will do mender-artifact write + mender-artifact upload) but I know by a fact from your internal point of view this is adding even more dependencies to an already bloated tool that is used from microservices (hence security sensitive). Not to mention yocto compatibility with its old golang versions around...

It is not my decision to make 😉

@merlin-northern merlin-northern self-assigned this Jul 6, 2026

@merlin-northern merlin-northern left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you very much for continuing interest in the Mender Project, and for using it. Thank you also for the contribution. The proper way of doing what you need is:

diff --git a/cmd/root.go b/cmd/root.go
index 8f5e0f3..9e30221 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -39,6 +39,8 @@ const (
 )
 
 func init() {
+       viper.SetEnvPrefix("MENDER_CLI")
+       viper.BindEnv(argRootServer)
        viper.SetConfigName(".mender-clirc")
        viper.SetConfigType("json")
        viper.AddConfigPath("/etc/mender-cli/")

and persisting the MENDER_CLI_SERVER_URL in your shell rc/env managing system you use.

If you are still willing to provide that contribution I am willing to listen to it. Note that the AutomaticEnv is something to consider, as well as documenting the new behaviour (consider what if someone had had MENDER_CLI_* vars declared, and now they start being parsed by the new version of mender-cli -- there are also ways around it). Please also do not leave the "MENDER_CLI" as a magic string.

Comment thread cmd/login_config.go
Comment on lines +77 to +81
if err != nil {
return ".mender-clirc"
}

return filepath.Join(home, ".mender-clirc")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

magic string -- now in 3 places in total in the code, not counting the tests (another 8)

Comment thread cmd/login_config.go
// if it does not exist. If the file already contains a "server" key, it is
// left untouched and false is returned. Otherwise the key is written and true
// is returned.
func persistServerToConfig(path string, server string) (bool, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this call overwrites a config file. what if there is another mender-cli running? what if my wife is editing my config file at the same time with ed? what if I had my super secret server url there, which is now lost forever, do we have it saved somewhere? what if i had a config file in some other location? /etc ~/ or .? btw the modern distros use ~/.confg. what if I am very particular about what is created and where? now mender-cli created directories, files, and all that? have I been warned? where is it documented?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

this call overwrites a config file. what if there is another mender-cli running? what if my wife is editing my config file at the same time with ed? what if I had my super secret server url there, which is now lost forever, do we have it saved somewhere? what if i had a config file in some other location? /etc ~/ or .?

Are you concerned about the race condition of two instances writing this file (which fair enough I ignored it for the sake of simplicity) or are you concerned about the fallback of config files?

For the later (which is the only that I think we should be concerned about) mender-cli already looks for three different places: /etc, working dir, and home. The logic in this PR tries to find any of these, if existing, and adds the server url. if none exists it defaults to the home vesion.

btw the modern distros use ~/.confg. what if I am very particular about what is created and where?

Please be specific: what are you asking for with this comment?

  • The .config is already mising from the tool and sounds like a huge scope creep.
  • "If I am very particular..." I guess that means that you want an option to decide on where to create the config file? But then with even less effort you can crate the file yourself if you are so particular

now mender-cli created directories, files, and all that? have I been warned?

mender-cli already creates directories and files to save the auth token, no warning

where is it documented?

Can you please guide me on where to document it? --help?

@lluiscampos lluiscampos left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thank you very much for continuing interest in the Mender Project, and for using it. Thank you also for the contribution.

Thank you very much for continuing developing such a great product. Thank you also for reviewing my contribution.

The proper way of doing what you need is:

diff --git a/cmd/root.go b/cmd/root.go
index 8f5e0f3..9e30221 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -39,6 +39,8 @@ const (
 )
 
 func init() {
+       viper.SetEnvPrefix("MENDER_CLI")
+       viper.BindEnv(argRootServer)
        viper.SetConfigName(".mender-clirc")
        viper.SetConfigType("json")
        viper.AddConfigPath("/etc/mender-cli/")

and persisting the MENDER_CLI_SERVER_URL in your shell rc/env managing system you use.

If you are still willing to provide that contribution I am willing to listen to it. Note that the AutomaticEnv is something to consider, as well as documenting the new behaviour (consider what if someone had had MENDER_CLI_* vars declared, and now they start being parsed by the new version of mender-cli -- there are also ways around it). Please also do not leave the "MENDER_CLI" as a magic string.

I don't understand... do you mean to apply this diff (viper is a a box of surprises for me!) instead of the rest of the PR? Please be explicit.

Comment thread cmd/login_config.go
// if it does not exist. If the file already contains a "server" key, it is
// left untouched and false is returned. Otherwise the key is written and true
// is returned.
func persistServerToConfig(path string, server string) (bool, error) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

this call overwrites a config file. what if there is another mender-cli running? what if my wife is editing my config file at the same time with ed? what if I had my super secret server url there, which is now lost forever, do we have it saved somewhere? what if i had a config file in some other location? /etc ~/ or .?

Are you concerned about the race condition of two instances writing this file (which fair enough I ignored it for the sake of simplicity) or are you concerned about the fallback of config files?

For the later (which is the only that I think we should be concerned about) mender-cli already looks for three different places: /etc, working dir, and home. The logic in this PR tries to find any of these, if existing, and adds the server url. if none exists it defaults to the home vesion.

btw the modern distros use ~/.confg. what if I am very particular about what is created and where?

Please be specific: what are you asking for with this comment?

  • The .config is already mising from the tool and sounds like a huge scope creep.
  • "If I am very particular..." I guess that means that you want an option to decide on where to create the config file? But then with even less effort you can crate the file yourself if you are so particular

now mender-cli created directories, files, and all that? have I been warned?

mender-cli already creates directories and files to save the auth token, no warning

where is it documented?

Can you please guide me on where to document it? --help?

@boeboe

boeboe commented Jul 18, 2026

Copy link
Copy Markdown

For those missing a lot of functionality in mender-cli... we eventually decided to fork the cli at https://github.com/Octave-Energy/mender-cli ... because there was unfortunately no feedback on a trivial PR.

It has first-class PAT token support, artifact/release/deployment/device/inventory support and a solid rewrite of the code base, based on the latest Openapi specs.

mender-cli
├── login                       Log in with username/password (optional 2FA)
├── token                       Manage the locally stored auth token
│   ├── set                     Persist a token (e.g. a Personal Access Token)
│   ├── show                    Print the stored token (decoded JWT / JSON / raw)
│   ├── path                    Print the default token storage path
│   └── clear                   Delete the stored token
├── artifacts                   Operations on Mender artifacts
│   ├── list                    List artifacts (filterable)
│   ├── upload                  Upload an artifact
│   ├── download                Download an artifact by id
│   └── delete                  Delete an artifact by id
├── devices                     Operations on Mender devices
│   ├── list                    List devices from device auth
│   ├── get                     Show one device from device auth (by id or filter)
│   └── count                   Count devices from device auth (by status)
├── releases                    Operations on Mender releases
│   ├── list                    List releases (filterable)
│   └── get                     Show one release by name
├── deployments                 Operations on Mender deployments
│   ├── list                    List deployments (filterable)
│   ├── count                   Count deployments (filterable)
│   ├── search                  Find deployments by targeted group or device
│   ├── get                     Show one deployment by id
│   ├── stats                   Show per-status device counts for a deployment
│   ├── devices                 List a deployment's devices and status
│   └── log                     Show a device's deployment log
├── inventory                   Device inventory (reported attributes and tags)
│   ├── devices
│   │   ├── list                List devices + inventory
│   │   ├── get                 Show one device's inventory (by id or filter)
│   │   └── count               Count devices matching inventory filters
│   ├── device-tags             Manage device tags (list/add/set/delete)
│   └── groups
│       └── list                List inventory static group names
├── terminal                    Remote terminal session on a device
├── port-forward                Forward local ports to a device (TCP/UDP)
├── cp                          Copy files to/from a device
├── version                     Print version and build information
└── completion                  Generate shell completion scripts

We are using it in production (both developers and operational folks) for several months now.
Binaries for Windows/MacOS/Linux amd64/arm64 available at the release section.

@boeboe

boeboe commented Jul 18, 2026

Copy link
Copy Markdown

Hello bakend server team 👋

I am using eu.hosted.io for my PoC and this feature helps me greatly to not end up having annoying 401 because I forgot to specify the server url.

From a user point of view, if I do "mender-cli login ..." and the valid token is saved in disk, it would be nice that the info about where the token is valid (server url) is also saved in disk.

Hi @lluiscampos ...

Check out our fork at https://github.com/Octave-Energy/mender-cli. I would be more then happy to incorporate any useful/missing feature you might think of.

➜  mender-cli git:(master) mender-cli token show
Using configuration file: /Users/me/.mender-clirc
Header:
  Algorithm:  RS256
  Key ID:     0
  Type:       JWT
Payload:
  Add-ons:     [{"enabled":false,"name":"configure"},{"enabled":true,"name":"troubleshoot"},{"enabled":false,"name":"monitor"}]
  Issued At:   2026-04-27T11:42:08Z (82 days ago)
  Issuer:      eu.hosted.mender.io
  JWT ID:      47565c20-737c-409b-88f8-551a62b25936
  Not Before:  2026-04-27T11:42:08Z (82 days ago)
  Plan:        os
  Scope:       mender.*
  Subject:     be64faf2-d77f-4920-9e0a-52b9af50668d
  Tenant:      66849d4850f45af02136eb2f
  Trial:       false
  User:        true

From a user point of view, if I do "mender-cli login ..." and the valid token is saved in disk, it would be nice that the info about where the token is valid (server url) is also saved in disk.

As you can see, that information is available in the JWT token itself... the issuer.

After a successful login with an explicit --server pointing to something different than hosted.mender.io, create or update .mender-clirc for further commands to use.

Is your use case that your are regularly switching between servers? In that case it might be useful to save multiple tokens (valid on different servers) and create a mender-cli alias that points to the correct per-server token.

alias mender-server1='mender-cli --token /path/to/server1.jwt'
alias mender-server2='mender-cli --token /path/to/server2.jwt'

@lluiscampos

lluiscampos commented Jul 31, 2026

Copy link
Copy Markdown
Author

Hi @boeboe and thank you very much for shipping in into the discussion and for your effort in improving the tool with your fork.

Open source projects like Mender have a lot of different stakeholders to satisfy, and it is hard to keep up with all contributed PRs. Granted... I am a bit frustrated with this trivial PR... but having been there I have quite a bit of empathy for the guys 😊

I've been trying your fork. See answers below.

(...)

From a user point of view, if I do "mender-cli login ..." and the valid token is saved in disk, it would be nice that the info about where the token is valid (server url) is also saved in disk.

As you can see, that information is available in the JWT token itself... the issuer.

Note that this is true for hosted Mender, but on an Open Source installation the issuer is Mender Users which is not very helpful. I will try to figure out how to fix this from the server side.

$ mender-cli token show | grep Issuer
  Issuer:      Mender Users

After a successful login with an explicit --server pointing to something different than hosted.mender.io, create or update .mender-clirc for further commands to use.

Is your use case that your are regularly switching between servers? In that case it might be useful to save multiple tokens (valid on different servers) and create a mender-cli alias that points to the correct per-server token.

Actually my use case was not different servers but just EU hosted Mender (which is the frustrating part: this is an official hosted Mender installation and mender-cli official tool should treat it as a first class use case!)

Unfortunately, your code won't work out of the box. See the following:

$ mender-cli token show | grep Issuer
time=2026-07-31T10:04:23.623+02:00 level=INFO msg="Configuration file not found. Continuing."
  Issuer:      eu.hosted.mender.io
$ mender-cli --token .cache/mender/authtoken artifacts list | wc -l
time=2026-07-31T10:04:27.414+02:00 level=INFO msg="Configuration file not found. Continuing."
FAILURE: GET /api/management/v2/deployments/artifacts?page=1&per_page=500 request failed with status 401
0
$ mender-cli --token .cache/mender/authtoken --server https://eu.hosted.mender.io artifacts list | wc -l
time=2026-07-31T10:04:30.473+02:00 level=INFO msg="Configuration file not found. Continuing."
369

So I would still need to modify the code in your fork for the tool to get the server url from the token issuer (when existing). I believe this is quite a deep change so I rather stick to the config file modification.

@lluiscampos

Copy link
Copy Markdown
Author

(...) I will try to figure out how to fix this from the server side.

See mendersoftware/mender-helm#743

@lluiscampos

Copy link
Copy Markdown
Author

@merlin-northern 🏓

@boeboe

boeboe commented Aug 13, 2026

Copy link
Copy Markdown

Hi @boeboe and thank you very much for shipping in into the discussion and for your effort in improving the tool with your fork.

Open source projects like Mender have a lot of different stakeholders to satisfy, and it is hard to keep up with all contributed PRs. Granted... I am a bit frustrated with this trivial PR... but having been there I have quite a bit of empathy for the guys 😊

I've been trying your fork. See answers below.

(...)

From a user point of view, if I do "mender-cli login ..." and the valid token is saved in disk, it would be nice that the info about where the token is valid (server url) is also saved in disk.

As you can see, that information is available in the JWT token itself... the issuer.

Note that this is true for hosted Mender, but on an Open Source installation the issuer is Mender Users which is not very helpful. I will try to figure out how to fix this from the server side.

$ mender-cli token show | grep Issuer
  Issuer:      Mender Users

After a successful login with an explicit --server pointing to something different than hosted.mender.io, create or update .mender-clirc for further commands to use.

Is your use case that your are regularly switching between servers? In that case it might be useful to save multiple tokens (valid on different servers) and create a mender-cli alias that points to the correct per-server token.

Actually my use case was not different servers but just EU hosted Mender (which is the frustrating part: this is an official hosted Mender installation and mender-cli official tool should treat it as a first class use case!)

Unfortunately, your code won't work out of the box. See the following:

$ mender-cli token show | grep Issuer
time=2026-07-31T10:04:23.623+02:00 level=INFO msg="Configuration file not found. Continuing."
  Issuer:      eu.hosted.mender.io
$ mender-cli --token .cache/mender/authtoken artifacts list | wc -l
time=2026-07-31T10:04:27.414+02:00 level=INFO msg="Configuration file not found. Continuing."
FAILURE: GET /api/management/v2/deployments/artifacts?page=1&per_page=500 request failed with status 401
0
$ mender-cli --token .cache/mender/authtoken --server https://eu.hosted.mender.io artifacts list | wc -l
time=2026-07-31T10:04:30.473+02:00 level=INFO msg="Configuration file not found. Continuing."
369

So I would still need to modify the code in your fork for the tool to get the server url from the token issuer (when existing). I believe this is quite a deep change so I rather stick to the config file modification.

The errors seem fairly clear to me: mender-cli needs a configuration file pointing to the server you want to use. This is documented behavior, so I’m not sure the commands above demonstrate an actual issue with the CLI.

FWIW, we use EU Hosted Mender as well and haven't had any problems with this setup. In fact, your last command also shows that the EU endpoint works correctly once the server is specified. And as documented, you normally don't need to pass --token explicitly on every invocation either.

Looking back at the original request, I think the important distinction is between runtime options and persistent configuration. --server is a global command-line flag for that particular invocation. Having it implicitly modify or overwrite persistent configuration in .mender-clirc would be somewhat surprising behavior for a CLI, so I can understand why that particular PR was not accepted.

If there is a desire to manage persistent configuration through the CLI, I think a more conventional approach would be something along the lines of:

mender-cli config set server https://eu.hosted.mender.io

and perhaps corresponding config get / config unset commands. That would make the intent explicit and would follow patterns used by many other CLI tools.

That said, given that the configuration file is very simple and that most users—including in your described use case—aren't regularly switching between Mender servers, I'm not sure there is currently enough benefit to justify adding that functionality.

So I think there are really two separate questions here:

  1. Should EU Hosted Mender work as a first-class supported endpoint? Absolutely—and it already does when configured.
  2. Should passing --server automatically persist that value into the configuration file? Personally, I don't think that would be a particularly intuitive CLI design.

A dedicated configuration command would be a cleaner way to solve the latter if there is sufficient demand for it.

@lluiscampos

Copy link
Copy Markdown
Author

Hi @boeboe And thanks for the good discussion

I’m not sure the commands above demonstrate an actual issue with the CLI.

Right, from your first feedback:

As you can see, that information is available in the JWT token itself... the issuer.

I understood it as "we can take the JWT issuer to figure out the correct server URL" -> so what I was trying to demonstrate is that it didn't work that way with your fork (but yeah, now I understand that I got your suggestion wrong in the first place)

Should EU Hosted Mender work as a first-class supported endpoint? Absolutely—and it already does when configured.

That is true for any server, not EU hosted Mender. As long as you write the url in the config file, is treated the same way. But that is not the case for USA hosted Mender, which is the default and works with no config. (which is obvious, the tool must have one single default)

Should passing --server automatically persist that value into the configuration file? Personally, I don't think that would be a particularly intuitive CLI design.

I disagree. As an example when I do "docker login my.custom.registry" the tool saves some metadata together with the token so that when I do "docker pull" I don't need to specify the registry in the CLI options (nor manually write a config file). My point was not on the just passing --server, but the fact that login command already saves a token so it should remember the url of the server that token belongs.

I am closing the PR nevertheless - it's is aging badly 👴

If the team at Mender sees any value on this they are free to pick it up.

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.

4 participants