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:
- an exact match, case-insensitive — taken silently, because you meant it;
- otherwise every name that is either similar enough (Jaro-Winkler ≥ 0.5) or contains what you typed;
- 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.
- Make a directory,
isard/, with a.envholding your credentials and a.gitignoreholding.env. - Write a shell script
up.shthat creates and starts three Ubuntu Server machines,box-1tobox-3, waiting for each. - Write
down.shthat stops and deletes all three without asking. - 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.