From 4f7af4948de0c44039005ca1fb5d1d07706924d2 Mon Sep 17 00:00:00 2001 From: benoit-cty Date: Fri, 28 Aug 2026 16:21:12 +0200 Subject: [PATCH 1/2] [Doc] Linux Service improvement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs/how-to/linux-service.md:48-73 — Step 3 rewritten to run login/config as your own user, explains why the service user can't, and adds the headless case (ssh -L 8090:localhost:8090, since the callback must reach localhost:8090 on the machine running the CLI). Step 6 now says to copy the values from ~/.codecarbon.config. codecarbon/cli/auth.py:172 — always prints the authorization URL, so a failed webbrowser.open (which returns True even when xdg-open bails, as it did for you) is no longer a dead end. codecarbon/cli/auth.py:88-98 — _save_credentials turns the raw Errno 13 into a message naming the resolved path and telling you to run from a writable directory. --- codecarbon/cli/auth.py | 12 ++++++++++-- docs/how-to/linux-service.md | 28 +++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/codecarbon/cli/auth.py b/codecarbon/cli/auth.py index 72460e81f..197246551 100644 --- a/codecarbon/cli/auth.py +++ b/codecarbon/cli/auth.py @@ -86,8 +86,15 @@ def _discover_endpoints(): def _save_credentials(tokens): """Save OAuth tokens to the local credentials file.""" - with open(_CREDENTIALS_FILE, "w") as f: - json.dump(tokens, f) + try: + with open(_CREDENTIALS_FILE, "w") as f: + json.dump(tokens, f) + except OSError as e: + raise ValueError( + f"Could not write the credentials file {_CREDENTIALS_FILE.resolve()} " + f"(error: {e}). Please run the command from a directory you can " + "write to." + ) def _load_credentials(): @@ -169,6 +176,7 @@ def authorize(): server = HTTPServer(("localhost", _REDIRECT_PORT), _CallbackHandler) print("Opening browser for authentication...") + print(f"If no browser opens, copy this URL into your browser:\n{uri}") webbrowser.open(uri) server.handle_request() diff --git a/docs/how-to/linux-service.md b/docs/how-to/linux-service.md index d5bfac1a9..494e5780e 100644 --- a/docs/how-to/linux-service.md +++ b/docs/how-to/linux-service.md @@ -45,16 +45,33 @@ Install CodeCarbon in the virtual environment: sudo -u codecarbon /opt/codecarbon/.venv/bin/pip install codecarbon ``` -### Step 3: Authenticate with CodeCarbon +### Step 3: Get Your Dashboard Credentials -Go to and create an account to get your API key. Then authenticate locally: +Go to and create an account. -Configure CodeCarbon: +Run the login and configuration wizard **as your own user**, not as the `codecarbon` +service user: `codecarbon login` needs a web browser and writes a `credentials.json` +file in the current directory, two things the service user does not have. The service +itself never uses those credentials, only the `api_key` you will write in its +configuration file at Step 6. + +From your own account, in a directory you can write to: ``` bash -sudo -u codecarbon /opt/codecarbon/.venv/bin/codecarbon login +pip install codecarbon # or run it with `uvx codecarbon` +codecarbon login +codecarbon config ``` +`codecarbon config` asks you to pick or create an organization, a project and an +experiment, then writes their ids and an API key to `~/.codecarbon.config`. Keep that +file at hand, you will copy its values into the service configuration at Step 6. + +If the machine has no browser (headless server), `codecarbon login` prints the +authentication URL: open it in a browser on the same machine, or forward the callback +port over SSH with `ssh -L 8090:localhost:8090 user@server` and open the URL on your +laptop, so that the `http://localhost:8090/callback` redirect reaches the CLI. + ### Step 4: Create a Systemd Service File Create the service configuration file for systemd: @@ -107,7 +124,8 @@ group means no other local account gains anything from this change. ### Step 6: Create the CodeCarbon Configuration File -Configure CodeCarbon with your dashboard credentials: +Copy the ids and the API key from the `~/.codecarbon.config` written at Step 3 into the +service configuration file: ``` bash sudo tee /opt/codecarbon/.codecarbon.config < Date: Fri, 28 Aug 2026 17:02:31 +0200 Subject: [PATCH 2/2] coverage --- tests/cli/test_cli_auth.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/cli/test_cli_auth.py b/tests/cli/test_cli_auth.py index 058a1cd45..df56756a8 100644 --- a/tests/cli/test_cli_auth.py +++ b/tests/cli/test_cli_auth.py @@ -90,6 +90,14 @@ def test_save_and_load_credentials(self, mock_open): loaded = auth._load_credentials() self.assertEqual(loaded, tokens) + @patch("builtins.open", side_effect=PermissionError(13, "Permission denied")) + def test_save_credentials_unwritable_directory(self, mock_open): + with self.assertRaises(ValueError) as ctx: + auth._save_credentials({"access_token": "a"}) + message = str(ctx.exception) + self.assertIn(str(auth._CREDENTIALS_FILE.resolve()), message) + self.assertIn("Permission denied", message) + @patch("codecarbon.cli.auth.requests.get") @patch("codecarbon.cli.auth.KeySet.import_key_set") @patch("codecarbon.cli.auth.jose_jwt.decode")