Skip to content
Documentation pages

Documentation

CLI

Create, start, open and destroy IsardVDI desktops from the terminal with the isard command.

Builds on Desktop.

Introduction

Everything on Desktop works.

It is also eleven clicks, and you are going to do it every day.

isard is a command that does the same things from the terminal:

$ isard create dev --template ubuntu
$ isard start dev --wait
$ isard ssh dev

Three lines, and the third one drops you into a shell on a machine that did not exist when you started typing.

It is a Python program that talks to the same API the Generalitat's website talks to.

There is no second platform and no privileged access: anything isard can do, you could do by clicking, and anything it cannot do, you still have to click.

Working environment

isard is published on PyPI, so install it as a global tool with Tool:

$ uv tool install isard
Resolved 24 packages in 412ms
Installed 24 packages in 63ms
Installed 1 executable: isard

Check that it is there:

$ isard version
isard 0.3.0

It needs Python 3.11 or newer.

You do not have to go and get one: uv downloads a suitable Python for the tool if the system's is older, which is most of the point of installing it this way.

To update it later:

$ uv tool upgrade isard

Signing in

Every command needs your Generalitat account — the same one you typed into the browser on the previous page.

Run one and see:

$ isard list
Gencat username (@edu.gencat.cat is added for you): 12345678a
Password:

That works, and you will get tired of it by Wednesday.

Put the credentials in the environment instead:

$ export GENCAT_USERNAME="12345678a"
$ export GENCAT_PASSWORD="la-teva-contrasenya"

The @edu.gencat.cat suffix is added for you, so the username is the short form — your DNI, letter included.

Environment variables die with the terminal, so the form you actually want is a .env file in the directory you work in:

GENCAT_USERNAME=12345678a
GENCAT_PASSWORD=la-teva-contrasenya

isard reads it from the current directory on every run.

Where the session lives

Signing in properly is slow: it is a SAML dance through the Generalitat's identity provider, several redirects deep, and it takes a couple of seconds.

Doing that on every command would be unbearable, so isard does it once and caches the result:

~/.config/isard/session.json the Gencat session
~/.config/isard/isardvdi.json the IsardVDI token

Both are written chmod 600 — readable by you and nobody else.

On Windows they are under %APPDATA%\isard\.

The session checks itself before every call and renews itself when it has expired, so you never have to think about this.

isard login forces a fresh sign-in and prints what it got:

$ isard login
Authenticating with Gencat...
Gencat session cached at /Users/david/.config/isard/session.json
Authenticating with IsardVDI...
IsardVDI session cached at /Users/david/.config/isard/isardvdi.json

-- Gencat Session --
Username       : 12345678a@edu.gencat.cat
Display name   : David de Mingo
Email          : david@optersoft.com
Organization   : Institut
Session ID     : 8f2a1c4e-...
Expires at     : 2026-08-14 19:42:11 UTC
Expires in     : 07:58:22
Gencat valid   : Yes
IsardVDI valid : Yes

And isard logout throws both away:

$ isard logout
Logged out successfully

That is the command to reach for on a shared machine, and the one to try when something behaves as if you were somebody else.

What have I got

isard list is the Desktops view, as text:

$ isard list
NAME   STATE    IP               OS
box-1  Started  10.2.76.37       Ubuntu 24.04
box-2  Stopped  -                Ubuntu 24.04
dev    Started  192.168.122.214  Fedora 44

Four columns, sorted by name.

A - in the IP column means the machine is stopped, or that it is running and has not reported an address — the case from the previous page.

Add --hardware and it goes and asks what each one is made of:

$ isard list --hardware
NAME   STATE    IP               OS            CPU     MEMORY
box-1  Started  10.2.76.37       Ubuntu 24.04  2 vCPU  4096 MB
box-2  Stopped  -                Ubuntu 24.04  2 vCPU  4096 MB
dev    Started  192.168.122.214  Fedora 44     4 vCPU  8192 MB

That is two extra columns and one API call per template, so it is a little slower.

It is also the command that answers why have I run out of quota.

Which templates are there

Before you can create anything you need to know what to create it from:

$ isard template

-- Available Templates --
NAME                     DESCRIPTION                     OS
fedora-server-44         Fedora Server, netinst          Fedora
ubuntu-24-04-desktop     Ubuntu 24.04 Desktop            Ubuntu
ubuntu-24-04-server      Ubuntu 24.04 Server             Ubuntu
windows-11-t3            Windows 11                      Windows

Your centre's list is longer than that and most of it is not what you are looking for, so narrow it:

$ isard template --filter ubuntu

-- Available Templates (filtered by 'ubuntu') --
NAME                  DESCRIPTION           OS
ubuntu-24-04-desktop  Ubuntu 24.04 Desktop  Ubuntu
ubuntu-24-04-server   Ubuntu 24.04 Server   Ubuntu

--filter is a plain case-insensitive substring on the name.

--hardware works here too, and it is worth a look: it tells you how big a machine each template makes before you make one.

$ isard template --filter ubuntu --hardware

-- Available Templates (filtered by 'ubuntu') --
NAME                  DESCRIPTION           OS      CPU     MEMORY   DISK
ubuntu-24-04-desktop  Ubuntu 24.04 Desktop  Ubuntu  2 vCPU  4096 MB  40 GB
ubuntu-24-04-server   Ubuntu 24.04 Server   Ubuntu  2 vCPU  4096 MB  40 GB

Templates your centre has disabled do not appear at all.

If a template you were told about is missing, that is why — and it is an Àtom ticket, not a bug.

Creating a desktop

$ isard create dev --template ubuntu-24-04-server
Matched template: 'ubuntu-24-04-server'
✓ Desktop 'dev' created successfully!

Two vCPU, 4 GB of RAM, and both the Default and the Wireguard interfaces — the same shape as the form's defaults, except that the Wireguard one is always on.

That last part is deliberate, and it is the reason the tool is pleasant to use: the interface you forget to tick is the interface you need forty minutes later, and adding it afterwards costs a restart.

Leave --template out and you get the list to pick from:

$ isard create dev
Select a template:
  1. fedora-server-44 [Fedora]
  2. ubuntu-24-04-desktop [Ubuntu]
  3. ubuntu-24-04-server [Ubuntu]
  4. windows-11-t3 [Windows]
Select (1-4): 3

Which is fine by hand and useless in a script, so --filter narrows that list the same way it narrows template.

You do not have to type the name correctly

Look at this again:

$ isard create dev --template ubuntu-24-04-server

Nobody types that twice.

So isard does not require it. Try:

$ isard create dev --template ubuntu

There are two templates with ubuntu in the name, so it asks:

$ isard create dev --template ubuntu
Select a template:
  1. ubuntu-24-04-desktop [Ubuntu]
  2. ubuntu-24-04-server [Ubuntu]
Select (1-2): 2

Now try a name that is not ambiguous:

$ isard create dev --template fedora
Matched template: 'fedora-server-44'
✓ Desktop 'dev' created successfully!

One candidate, so it says which one it picked and gets on with it.

And now type it wrong on purpose:

$ isard start bx-1
Matched: 'box-1'

The rule underneath is three steps, in order:

  1. an exact match, case-insensitive — taken silently, because you meant it;
  2. otherwise every name that is either similar enough (Jaro-Winkler ≥ 0.5) or contains what you typed;
  3. that list, sorted by how close it is.

One survivor is used, with a Matched: line so you can see what happened. Two or more open the picker. None is an error that lists every desktop you have, which is usually enough to see the typo yourself.

A short slice is a valid name. isard start box finds box-1 if it is the only one.

Starting and stopping

$ isard start dev
✓ Desktop 'dev' starting

The command returns immediately, because starting a machine takes a while and there is no reason to hold your terminal hostage.

Which is exactly wrong when the next thing you do is connect to it:

$ isard start dev
$ isard ssh dev
Error: connection refused

The machine was not up yet.

--wait fixes that — the command does not return until the desktop is running:

$ isard start dev --wait
⠹ Waiting for 'dev' to start...
✓ Desktop 'dev' is running

--wait is what makes the tool scriptable. Without it every line after start is a race.

The default patience is 120 seconds. A big machine, or a busy afternoon, wants more:

$ isard start dev --wait --timeout 300

Stopping is the same shape:

$ isard stop dev --wait
⠹ Waiting for 'dev' to stop...
✓ Desktop 'dev' is stopped

TaskWait for it

Start box-1 twice: once without --wait and once with it.

Time both.

Show the solution
$ time isard start box-1
✓ Desktop 'box-1' starting

real	0m1.9s

$ isard stop box-1 --wait
$ time isard start box-1 --wait
✓ Desktop 'box-1' is running

real	0m24.3s

The first one is not faster. It just stopped measuring.

Booking

Some desktops cannot be started on demand — a GPU one is the usual case.

$ isard start gpu-box
Error: this desktop needs a booking before it can start.

That is not a failure, it is a queue. --book reserves the slot and then starts:

$ isard start gpu-box --book --wait
✓ Booked 60 minutes
✓ Desktop 'gpu-box' is running

--book-minutes changes the hour. That is GPU.

Deleting

$ isard delete dev
Delete desktop 'dev'? [y/N]:

The prompt defaults to no, which is the right default for anything with a delete in it.

Answer y and the desktop goes to the recycle bin, where it can still be restored.

Two flags change that, and they belong together:

$ isard delete dev --permanent --yes
✓ Desktop 'dev' deleted permanently

--permanent skips the bin. --yes skips the question.

Both are good in a script and bad by hand. A script that stops to ask is a script that hangs; a person who is not asked is a person who deletes last week's work.

Opening it

isard view opens the desktop's screen:

$ isard view dev
Matched: 'dev'
✓ Desktop is running
Launching remote-viewer...

It does the whole .vv dance from the previous page for you — fetches the SPICE file, decodes the certificate, launches remote-viewer — and if the desktop was stopped it starts it first and waits.

If remote-viewer is not installed, it installs it:

Windows downloads and unpacks the MSI, no administrator needed
macOS Homebrew, from the jeffreywildman/homebrew-virt-manager tap
Fedora dnf install virt-viewer
Ubuntu, Debian apt install virt-viewer

--no-install turns that off, which is what you want if you would rather do it yourself.

Reaching it

isard ssh puts you in a shell on the machine:

$ isard ssh dev
isard@dev:~$

There is a great deal happening behind that one line — a key that has to be registered, a bastion on port 443, a desktop that gets started if it was stopped — and it is the whole of Bastion.

What matters here is the other form.

With a command after --, isard ssh is not interactive:

$ isard ssh dev -- uname -a
Linux dev 6.8.0-51-generic #52-Ubuntu SMP x86_64 GNU/Linux

That is the line that makes the whole tool worth having.

The command's output is the command's output, its exit code is its exit code, and everything isard itself prints goes to stderr — so a pipe never has to filter spinner lines out of your data:

$ isard ssh dev -- 'cat /etc/os-release' | grep VERSION_ID
VERSION_ID="24.04"

You can push a directory through it:

$ tar czf - src | isard ssh dev -- 'tar xzf - -C /home/isard'

A whole class in a loop

Put the pieces together and the point of a command over a form becomes obvious:

$ for n in 1 2 3; do
>   isard create box-$n --template ubuntu-24-04-server
>   isard start box-$n --wait
> done
✓ Desktop 'box-1' created successfully!
✓ Desktop 'box-1' is running
✓ Desktop 'box-2' created successfully!
✓ Desktop 'box-2' is running
✓ Desktop 'box-3' created successfully!
✓ Desktop 'box-3' is running

Three machines, one line, no clicking.

Tearing them down is the same loop backwards:

$ for n in 1 2 3; do isard delete box-$n --yes; done

And running the same command on all of them is one more:

$ for n in 1 2 3; do
>   echo "== box-$n"
>   isard ssh box-$n -- 'uptime'
> done
== box-1
 11:42:03 up 2 min,  0 users,  load average: 0.08, 0.03, 0.01
== box-2
 11:42:09 up 2 min,  0 users,  load average: 0.11, 0.04, 0.01
== box-3
 11:42:14 up 1 min,  0 users,  load average: 0.15, 0.05, 0.01

The whole command list

--help prints it, and it is worth reading once:

$ isard --help

 Usage: isard [OPTIONS] COMMAND [ARGS]...

 Manage IsardVDI virtual desktops on the Gencat educational platform.

╭─ Commands ───────────────────────────────────────────────────────────────────╮
│ login     Log in to Gencat (SAML) and IsardVDI, then show the session        │
│           summary.                                                           │
│ logout    Clear cached authentication sessions and per-desktop SSH           │
│           confirmations.                                                     │
│ version   Show the version of the isard CLI tool.                            │
│ list      List IsardVDI desktops for the authenticated user.                 │
│ build     Build a template from a YAML profile: install, configure, promote. │
│ create    Create a new desktop from an existing template.                    │
│ start     Start a stopped desktop.                                           │
│ stop      Stop a running desktop.                                            │
│ hardware  Show a desktop's vCPU and memory, or change them on a stopped      │
│           desktop.                                                           │
│ delete    Delete a desktop — move it to the recycle bin, or erase it         │
│           permanently.                                                       │
│ view      Open a desktop in remote-viewer over SPICE.                        │
│ ssh       Open an SSH session to a desktop via the IsardVDI bastion.         │
│ template  List available templates, or create one from media.                │
│ media     List, add, or delete media (the ISO library).                      │
│ seed      Build a local cloud-init / kickstart seed ISO.                     │
│ vpn       Join the desktop network over IsardVDI's WireGuard VPN.            │
╰──────────────────────────────────────────────────────────────────────────────╯

Every subcommand takes --help too, and that is the answer to what were the flags on start again.

hardware is new: it reads a desktop's vCPU and memory back, and on a stopped one it can resize both.

Four of these have a page of their own:

It is also a library

isard is a Python package, and everything the command does is importable:

from isard import get_desktops, login_to_isardvdi

session = login_to_isardvdi("12345678a@edu.gencat.cat", "la-teva-contrasenya")

for desktop in get_desktops(session):
    print(desktop.name, desktop.state)
box-1 Started
box-2 Stopped
dev Started

You will not often need this — a shell loop covers most of what a script wants.

It is worth knowing it exists the day you want to check thirty machines and report which ones are still running, because that is a program, not a loop.

Exercises

TaskFrom nothing to a shell

Starting with no desktops at all, get a shell on a Fedora machine called lab using only the terminal.

Show the solution
$ isard template --filter fedora

-- Available Templates (filtered by 'fedora') --
NAME              DESCRIPTION             OS
fedora-server-44  Fedora Server, netinst  Fedora

$ isard create lab --template fedora
Matched template: 'fedora-server-44'
✓ Desktop 'lab' created successfully!

$ isard start lab --wait
✓ Desktop 'lab' is running

$ isard ssh lab
[isard@lab ~]$

Four commands, and --template fedora was enough for the third one.

TaskWhich of my machines are running

Write a command that prints only the names of your started desktops.

Show the solution
$ isard list | awk '$2 == "Started" { print $1 }'
box-1
dev

isard list is a plain table on stdout, so it goes through awk, grep and wc like any other text.

That is the second reason for a command over a form, after the loop.

TaskDeliberate ambiguity

You have box-1, box-2 and box-3.

What happens with isard stop box? And with isard stop box-2?

Show the solution

The first opens a picker, because three desktops match:

$ isard stop box
Select a desktop:
  1. box-1 (Started)
  2. box-2 (Started)
  3. box-3 (Started)
Select (1-3):

The second is an exact match, so it is taken silently with no Matched: line at all.

Exact means exact: the fuzzy matcher never runs.

TaskTurn everything off, in one line

Stop every desktop you have without naming any of them.

Show the solution
$ isard list | awk 'NR > 1 && $2 == "Started" { print $1 }' | while read n; do
>   isard stop "$n" --wait
> done
✓ Desktop 'box-1' is stopped
✓ Desktop 'dev' is stopped

NR > 1 drops the header row.

Read the loop before you run it. It is three commands away from being one that deletes them instead, and --yes would not ask.

TaskThe session file

Find the two files isard caches your session in, and check their permissions.

Then log out and check again.

Show the solution
$ ls -l ~/.config/isard/
total 24
-rw-------  1 david  staff  1204 14 Aug 11:31 isardvdi.json
-rw-------  1 david  staff  2841 14 Aug 11:31 session.json

$ isard logout
Logged out successfully

$ ls -l ~/.config/isard/
total 0

-rw------- is 600: you, and nobody else on the machine.

Those files are a signed token, which is to say they are your session — anyone who can read one is you, until it expires.

Project

Set up a working directory you can come back to for the rest of the course.

  1. Make a directory, isard/, with a .env holding your credentials and a .gitignore holding .env.
  2. Write a shell script up.sh that creates and starts three Ubuntu Server machines, box-1 to box-3, waiting for each.
  3. Write down.sh that stops and deletes all three without asking.
  4. Make both executable and run them.
Show the solution
# up.sh
#!/bin/sh
set -e

for n in 1 2 3; do
    isard create "box-$n" --template ubuntu-24-04-server
    isard start "box-$n" --wait --timeout 300
done

isard list
# down.sh
#!/bin/sh

for n in 1 2 3; do
    isard delete "box-$n" --yes
done
$ chmod +x up.sh down.sh
$ ./up.sh
✓ Desktop 'box-1' created successfully!
✓ Desktop 'box-1' is running
...

Two things to notice.

set -e is in up.sh and not in down.sh: if creating the second machine fails you want to stop, but if deleting the second one fails you still want the third one gone.

And the template name is written out in full. --template ubuntu would have opened a picker, and a script has nobody to answer it.