Metadata-Version: 2.5
Name: isard
Version: 0.3.0
Summary: CLI tool for managing IsardVDI virtual desktops on the Gencat educational platform
Project-URL: Homepage, https://academy.optersoft.com/tool/isard
Project-URL: Repository, https://github.com/ddemingo/isard
Project-URL: Issues, https://github.com/ddemingo/isard/issues
Project-URL: Documentation, https://academy.optersoft.com/tool/isard
Author-email: "Optersoft, S.L." <david@optersoft.com>
License-Expression: MIT OR Apache-2.0
License-File: LICENSE-APACHE
License-File: LICENSE-MIT
Keywords: cli,education,gencat,isard,isardvdi,vdi,virtualization
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Education
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Terminals
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Requires-Dist: beautifulsoup4>=4.12
Requires-Dist: defusedxml>=0.7.1
Requires-Dist: httpx[brotli]>=0.28.1
Requires-Dist: paramiko>=3.5
Requires-Dist: pycdlib>=1.14
Requires-Dist: python-dotenv>=1.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.0
Requires-Dist: typer>=0.15
Description-Content-Type: text/markdown

# isard

[![PyPI](https://img.shields.io/pypi/v/isard.svg)](https://pypi.org/project/isard/)
[![Python](https://img.shields.io/pypi/pyversions/isard.svg)](https://pypi.org/project/isard/)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/License-MIT%20OR%20Apache--2.0-blue.svg)](#license)

Manage your IsardVDI virtual desktops on the Gencat educational platform
(`elmeuescriptori.gestioeducativa.gencat.cat`) from the command line.

```bash
isard login          # authenticate (SAML + JWT)
isard list           # your desktops
isard create dev     # a desktop from a template
isard start dev      # power on
isard view dev       # open in virt-viewer
isard ssh dev        # ssh into it
```

## Install

```bash
uv tool install isard      # recommended
pipx install isard
pip install isard
```

Requires Python 3.11 or newer. `uv tool install` fetches a suitable
Python for you if the system one is older.

Upgrade the same way you installed:

```bash
uv tool upgrade isard
pipx upgrade isard
pip install --upgrade isard
```

## Authenticate

Running any command prompts for your credentials. To skip the prompt,
set them in the environment:

```bash
export GENCAT_USERNAME="12345678a"     # @edu.gencat.cat is added for you
export GENCAT_PASSWORD="your-password"
```

…or drop a `.env` file in your working directory:

```env
GENCAT_USERNAME=12345678a
GENCAT_PASSWORD=your-password
```

Sessions are cached in `~/.config/isard/` (`%APPDATA%\isard\` on
Windows) with `chmod 600`, and refresh themselves when they expire.
`isard logout` clears them.

## Commands

| Command | Description | Options |
|---------|-------------|---------|
| `login` | Authenticate and display session info | — |
| `logout` | Delete cached sessions | — |
| `version` | Show the installed version | — |
| `list` | List desktops with state/IP/OS | `--hardware/-H`, `--name/-n` |
| `template` / `template list` | List available templates | `--category/-c`, `--hardware/-H`, `--filter` |
| `template create` | Pick a Fedora source, download the ISO, create `fedora-<variant>-<version>`, then detach + delete the media after install | `--seed-url` |
| `template promote` | Promote a stopped desktop into a template | `--name/-n`, `--description/-d`, `--disabled` |
| `build <profile.yaml>` | Build a template from a YAML profile: fetch the newest Ubuntu/Fedora ISO, hand you the installer in a viewer, then configure over SSH and promote | `--identity/-i`, `--keep-media`, `--no-promote`, `--no-viewer`, `--dry-run`, `--install-timeout` |
| `media` / `media list` | List your media (ISO library) | `--filter` |
| `media add <name>` | Register a new media by HTTPS URL | `--url`, `--description/-d`, `--no-wait`, `--timeout` |
| `media delete <name>` | Delete a media | `--yes/-y` |
| `seed cidata` | Build a NoCloud (CIDATA) seed ISO | `--user-data`, `--meta-data`, `--output/-o` |
| `seed oemdrv` | Build an Anaconda (OEMDRV) seed ISO | `--kickstart`, `--output/-o` |
| `create <name>` | Create a desktop from a template | `--template/-t`, `--description/-d`, `--category/-c`, `--filter` |
| `start <name>` | Start a stopped desktop | `--wait/-w`, `--timeout`, `--book/-b`, `--book-minutes` |
| `stop <name>` | Stop a running desktop | `--wait/-w`, `--timeout` |
| `delete <name>` | Delete a desktop (trash or permanent) | `--permanent`, `--yes/-y` |
| `view <name>` | Open the desktop in virt-viewer over SPICE | `--install/--no-install`, `--auto-resize`, `--debug` |
| `ssh <name>` | SSH in via the bastion | `--user/-l`, `--identity/-i`, `--password`, `--timeout`, `--dry-run` |
| `vpn` / `vpn status` | Is the WireGuard tunnel up? | — |
| `vpn config` | Download this account's WireGuard config | `--output/-o`, `--show` |
| `vpn up` / `vpn down` | Join or leave the desktop network (installs `wireguard-tools` if missing) | `--refresh/--no-refresh`, `--install/--no-install`, `--dry-run` |

Full reference with every flag and behaviour:
[`docs/commands.md`](docs/commands.md), also published at
<https://isard.xtec.dev/commands>.

### Examples

```bash
isard list --hardware                  # add CPU + memory columns
isard template --filter fedora         # narrow the template list
isard create dev --template fedora     # fuzzy: resolves to fedora-server-44
isard start dev --wait --timeout 300   # block until it's running
isard start gpu-box --book             # book a vGPU slot first, then start
isard delete dev --permanent --yes     # skip the trash and the prompt
isard media add ubuntu-24 --url https://releases.ubuntu.com/24.04/…-amd64.iso
```

### Name resolution

Desktop and template names resolve by exact match first, then
Jaro-Winkler fuzzy matching, then substring. A typo still finds the
right desktop; an ambiguous name opens a picker. In scripts (no TTY) an
ambiguous name is an error rather than a guess.

### SSH

`isard ssh` handles the plumbing: it resolves your private key
(`--identity`, `~/.ssh/id_ed25519`, `~/.ssh/id_rsa`, or generates a new
ed25519 key), registers the public key with the IsardVDI bastion, starts
the desktop if it's stopped, and connects through port 443.

The first connection to a desktop shows its guest credentials
half-masked and asks you to confirm them. Confirmed desktops are
remembered in `~/.config/isard/desktop-creds.json`, which holds ids
only — no passwords. Use `--password` to set new guest credentials (the
desktop is stopped, updated, and restarted).

If OpenSSH isn't installed, an embedded pure-Python client takes over.

### virt-viewer

`isard view` installs `remote-viewer` if it's missing:

| Platform | Method |
|----------|--------|
| Windows | Downloads and extracts the MSI from GitLab releases (no admin needed) |
| macOS | Homebrew via the `jeffreywildman/homebrew-virt-manager` tap (builds from source — takes several minutes) |
| Fedora | `dnf install virt-viewer` |
| Ubuntu/Debian | `apt install virt-viewer` |

Pass `--no-install` to skip that.

### Building a template from a file

`isard build` takes one YAML file and gives back a finished template —
newest ISO, a short manual install in a viewer, then packages and commands
applied over the SSH bastion, and promote:

```yaml
# ubuntu.yaml
image: { distro: ubuntu, variant: server, release: lts }
template: { name: ubuntu-lts-base, memory_gb: 8, disk_gb: 100 }
user:
  name: isard
  password_env: ISARD_TEMPLATE_PASSWORD
  ssh_key: ~/.ssh/id_ed25519.pub
packages: [git, curl, docker.io]
run:
  - systemctl enable --now docker
```

```bash
isard build ubuntu.yaml --dry-run   # see the plan without touching the API
isard build ubuntu.yaml
```

One step is yours: when the viewer opens, install the OS with the
profile's username and password and an SSH server — three fields on the
installer's own screens. Shut the machine down and the CLI takes over
again. Full reference: [`docs/build-profiles.md`](docs/build-profiles.md),
examples in [`examples/build/`](examples/build/).

### Unattended templates, by hand

The lower-level path is still there: build a seed ISO whose volume label
the installer auto-discovers (`CIDATA` for cloud-init, `OEMDRV` for
Anaconda), host it over HTTPS, and hand the URL to `template create`:

```bash
isard seed oemdrv --kickstart my-ks.cfg -o seed.iso
# host seed.iso somewhere IsardVDI can reach it
isard template create --seed-url https://your-host/seed.iso
```

Starter configs are in [`examples/seed/`](examples/seed/); the
end-to-end runbook is
[`docs/phase3-verification.md`](docs/phase3-verification.md).

## Use it as a library

Everything the CLI does is importable:

```python
from isard import get_desktops, login_to_isardvdi

session = login_to_isardvdi("12345678a@edu.gencat.cat", "…")
for desktop in get_desktops(session):
    print(desktop.name, desktop.state)
```

See [`docs/library-api.md`](docs/library-api.md).

## Development

```bash
git clone git@github.com:ddemingo/isard.git
cd isard
uv sync --dev
uv run pytest          # no network needed
uv run ruff check .
uv run isard --help
```

Design notes and invariants:
[`docs/architecture.md`](docs/architecture.md). Every IsardVDI endpoint
we call: [`docs/api-endpoints.md`](docs/api-endpoints.md).

## License

Licensed under either of [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE),
at your option. Unless you state otherwise, any contribution you submit for
inclusion is dual-licensed on those same terms.

Copyright © 2026 Optersoft, S.L.

---

Built for the Gencat educational platform — you'll need valid IsardVDI
credentials to use it.
