Metadata-Version: 2.5
Name: proto-lpm-cli
Version: 0.3.198
Summary: TEST VERSION of lpm-cli, a command-line client for Live Platform Manager, derived from its OpenAPI document
Author-email: Rey Rex <rey.r@gatti-services.com>
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.14
Requires-Dist: click>=8.1
Requires-Dist: dulwich>=1.2.15
Requires-Dist: httpx>=0.27.0
Requires-Dist: packaging>=26.3
Requires-Dist: rich>=13.0
Description-Content-Type: text/markdown

# lpm-cli

NOTE: NOT TO BE USED FOR ANY PURPOSE OTHER THAN DEV TESTING OF A FEATURE. This branch exists purely for testing the auto-update feature. We needed a downloadable branch whose git release tag can be updated at the repo.

----

Command-line client for Live Platform Manager.

The command tree is **not** written by hand: it is derived at runtime from the backend's OpenAPI
document, so every endpoint the server offers is reachable, and a new endpoint appears without a
CLI release. `lpm --help` shows what the server in force offers; what a browser cookie owns —
login, single sign-on, scoreboard, SOL console — is absent from the tree and reachable only with
`lpm raw`.

Answers arrive as a table in a terminal and as the server's own JSON anywhere else. `--json` asks
for the whole document, wherever on the line it is written.

Two documents in Outline go further than this file:
[LPM CLI](https://outline.ultrascale-svc.com/doc/lpm-cli-pcJe3UpCtO) walks through logging in,
reading, writing and what to do when a call fails, and
[LPM Patch Notes](https://outline.ultrascale-svc.com/doc/lpm-patch-notes-jewooRV4e0) records what
each LPM release brought.

## Install

```bash
uv tool install git+ssh://git@source.ultrascale-svc.com/UltrascaleDI/lpm-cli.git
lpm login
```

That is the whole of it if your SSH key is on `source.ultrascale-svc.com`. `lpm login` asks for an
API key made in the LPM web UI. No uv yet? `curl -LsSf https://astral.sh/uv/install.sh | sh`, then
open a new terminal — it brings its own Python.

- Add `@v0.1.0` to the repository to follow a release rather than `main`.
- The key is stored in `~/.config/lpm/config.toml` with mode `0600`, never as a command-line
  argument. One too long for a terminal line goes in with `lpm login --api-key-file <path>`.
- Windows is not supported, and is not planned: the config file's permissions are POSIX modes.
- `lpm cli update` fetches the current version from wherever this copy came from — a repository
  install reinstalled, an index install upgraded with the index named, a checkout left to
  `git pull`. `uv tool upgrade` is no substitute: it treats a Git source as settled and reports
  nothing to upgrade however many commits have landed.

<details>
<summary>Without an SSH key, or in CI</summary>

The registry is private, so this needs a Forgejo token (Settings → Applications). It goes in
`~/.netrc`, which keeps it out of the index URL; the `chmod` is not optional.

```bash
printf 'machine source.ultrascale-svc.com login USERNAME password TOKEN\n' >> ~/.netrc
chmod 600 ~/.netrc

uv tool install lpm-cli \
  --index https://source.ultrascale-svc.com/api/packages/UltrascaleDI/pypi/simple
```

The forge is asked first, so `lpm-cli` comes from there whenever it is reachable — but the name is
one the public index could carry one day, and there it would be someone else's package. If that
matters more than convenience, install from the repository. Dependencies come from the public
index either way, which is why it stays in the list.

</details>

## Driving a BMC

What LPM does over IPMI sits under `lpm bmc`. Every command there names its devices with
`--device-ids`, repeated once per device — as the id `lpm dcim devices list` gives, or as the
machine's name with an `@` in front of it:

```bash
lpm bmc power-status list --device-ids @node-17 --device-ids @node-18
lpm bmc power create --device-ids @node-17 --action graceful_restart
lpm bmc logs list --device-ids 42 --limit 20                # ids still work, everywhere
```

A name is looked up before the call goes out, and has to fit one machine exactly: `@node-1` does
not reach `node-17`, and a name two machines carry is refused with both of them named rather than
acted on. Where several are given and one of them cannot be pinned down, none of the call runs.
The mark is what makes a name a name — without it `42` is an id, even where a machine is called
that. This works wherever a command takes a device id, `lpm bmc` included but not only.

| Command                                          | What it reaches                                                         |
| ------------------------------------------------ | ----------------------------------------------------------------------- |
| `bmc ipmi-commands list`                         | which IPMI actions this backend offers                                  |
| `bmc power-status list`                          | the power state, per device                                             |
| `bmc power create --action <a>`                  | `on`, `off`, `restart`, `reset`, `graceful_shutdown`, `graceful_restart` |
| `bmc reset create`                               | resets the BMC itself, not the host                                     |
| `bmc info list`, `bmc health list`               | identity, and the health the BMC reports                                |
| `bmc logs list --limit <n>`                      | the event log                                                           |
| `bmc memory-modules list`                        | the DIMMs as the BMC sees them                                          |
| `bmc task-list list`, `bmc tasks-clear create`   | tasks the BMC is holding, and a way to drop them                        |
| `bmc media-mount create --image-url <u>`         | virtual media; `bmc media-eject create` takes it back off               |
| `bmc firmware list`, `bmc firmware-flash create` | the BMC's own firmware, with `bios-` and `gpu-` siblings for the rest   |
| `bmc bios-settings list`, `bmc bios-settings create -d @file` | BIOS attributes, written as one document           |
| `bmc fan-profiles list`, `bmc fan-profile activate create` | the fan policy in force, and how to change it         |

The actions — power, reset, media, the flashes, tasks-clear — take `--scheduled-at`, an ISO 8601
time with a zone: left out, the action runs now and the answer holds the outcome per device; set to
a future time, it is deferred and the answer holds only the action ids to poll. `lpm bmc <command>
--help` names the endpoint behind the command, the capability it needs, and every flag it takes.

## Completion

There is nothing to install. Every run drops the completion where fish and bash read one by
themselves and keeps it in step with the version running; a file at that place the tool did not
write is left where it is. zsh has no such place — nothing takes effect there without an entry in
`.zshrc`, and that file is yours — so the tool writes what it can and names the line, which goes
above `compinit`:

```bash
fpath=(~/.local/share/lpm/completions $fpath)
```

What completes is the tool's own commands. The tree derived from the server would cost a round trip
for every keystroke, so a name only the backend knows completes to nothing at all.

## Working against more than one LPM

```bash
lpm login --url https://lab.example --profile lab
lpm cli profile list          # which profiles exist, and which is in force
lpm cli profile use lab       # switch for the next commands
lpm --profile lab dcim devices list   # or just for this one call
lpm cli profile remove lab    # take it back off this machine
lpm logout                    # the same, for whichever is in force
lpm whoami                    # the server, and what this key may do
```

`LPM_URL` and `LPM_API_KEY` work too, for CI jobs with no config file. What is typed on the
command line wins over the environment, and both win over the file.

An address must be an address: `https://name:secret@lpm.example` is refused wherever it turns up,
because a name and password in a URL replace the API key as the credential for every call — the
key then never sent, and nothing saying so. For the same reason `lpm raw` takes a path, not a
whole address.

The address from the browser works. LPM answers on `lpm.<env>` with the web UI and on
`lpm-api.<env>` with the API, so where a page comes back instead of JSON, the tool retries against
the `lpm-api` sibling of that name, says so on stderr, and `login` stores the address that
answered. No other host is rewritten: guessing more freely means sending the key to one nobody
named.

Removing the profile in force leaves none chosen rather than quietly falling back to another — the
next command says so and lists what is left. What removal takes away is the copy stored here; the
key itself keeps working until it is revoked in the web UI.
