isard.xtec.dev

CLI command reference

Authoritative reference for every isard subcommand. For end-user examples see ../README.md; for the underlying API endpoints see api-endpoints.md.

Credentials come from GENCAT_USERNAME / GENCAT_PASSWORD (read from the environment, or from a local .env file via dotenvy). The username is auto-suffixed with @edu.gencat.cat if you pass only a local part. If either variable is unset, the CLI prompts on a TTY (and errors out otherwise). No subcommand takes credentials as flags.

~/.config/isard/session.json holds the Gencat SAML AuthSession; ~/.config/isard/isardvdi.json holds the IsardVDI IsardVdiSession JWT. Both are chmod 600 on POSIX. Sessions self-heal: validity is checked locally before every API call, and a 401 from any endpoint transparently clears the cache and re-authenticates (see architecture.md#session-handling).


login

Run the SAML + JWT flow and persist both sessions. Always re-authenticates fresh: any existing cache is cleared before authenticating. No arguments.

The @edu.gencat.cat suffix on the username is added automatically — type just the short form (e.g. 12345678a). A hint line is printed above the prompt as a reminder.

logout

Delete both cached session files and the per-desktop SSH credentials cache. No args.

version

Print package.version from Cargo.toml. No args.

update

isard update [--check] [--version <X.Y.Z>] [--base-url <URL>]

Replace this binary with the latest release from isard.xtec.dev.

isard self-update is a hidden alias kept for the v0.1.4 → v0.1.5 migration. Prefer isard update in new scripts.

Auto update-notification

Since v0.1.8 every isard <subcommand> (except update, version, login, and the hidden background subcommand) silently spawns a detached child that polls /api/version and writes the result to ~/.config/isard/update-check.json. On the next invocation, before the foreground subcommand runs, the CLI reads that file — if a newer release is pending it interactively asks Update now? [Y/n]. Yes runs isard update inline; No records a 24-hour per-version snooze (a brand-new release that lands during the snooze window re-prompts immediately, since the snooze is keyed to the offered version). The background check itself rate-limits to once per 6 hours. In non-TTY contexts (pipes, scripts) the prompt degrades to a one-line note: isard X.Y.Z is available — run \isard update`and never blocks. SetISARD_SKIP_UPDATE_CHECK=1` to opt out of the whole flow.

list

isard list [-H|--hardware] [-n|--name <NAME>]

template

The template subcommand is a group. With no action it defaults to list.

template list

isard template [-c|--category <ID>] [-H|--hardware] [--filter <NAME>]
isard template list ...

Calls IsardVdiClient::get_templates, which tries a fallback chain (see api-endpoints.md). --filter is a case-insensitive substring match on name. Disabled templates are hidden from the output.

template create

isard template create [--seed-url <URL>]

Only an optional --seed-url. The desktop name is derived deterministically from the picked Fedora variant and major version, so a re-run with the same Fedora release produces the same name and IsardVDI correctly returns 409 (“desktop already exists”). Naming scheme:

The command:

  1. Reuses or refreshes the IsardVDI session.
  2. Shows an interactive numbered menu:
    Template source:
      1. Fedora Server (Net Install)
      2. Fedora Workstation (Live)
    Select (1-2):
  3. Resolves the latest x86_64 ISO via Fedora’s releases.json (fedora::latest_fedora_iso). Filter: arch="x86_64", variant="Server" or "Workstation", link contains netinst (Server) or Live (Workstation), highest numeric version. The version is what the derived name uses.
  4. Computes the desktop name (fedora-<variant>-<version>) and prints it.
  5. Looks up media by filename stem (e.g. Fedora-Server-netinst-x86_64-44-1.7). Reuses if Downloaded; if missing, registers via POST /api/v3/media and polls /api/v3/media until status == "Downloaded" (timeout: 1800 s, poll: 5 s, transparent session refresh on 401).
  6. Queries /api/v3/media/installs and picks an xml_id (priority: fedora > linux > centos / rhel / alma / rocky, with virtio preferred within each family). Prints the full available list for visibility.
  7. Calls POST /api/v3/desktop/from/media with hardware constants (TEMPLATE_VCPUS=4, TEMPLATE_MEMORY_MB=8192 → sent as 8 GB, TEMPLATE_DISK_GB=100, TEMPLATE_DISK_BUS=virtio), boot_order=["iso"], interfaces=["default", "wireguard"], plus forced_hyp=false, favourite_hyp=false at top level.
  8. Starts the desktop so you can open the installer via isard view fedora-<variant>-<version>.
  9. Prompts Press Enter when installation is complete. Enter triggers stop → PUT /api/v3/domain/<desktop> with {hardware:{boot_order:["disk"], isos:[]}} (detach) → DELETE /api/v3/media/<id> (and the seed media if any). Ctrl-C leaves everything in place — re-running picks up the same media row.

Unattended install via --seed-url

isard template create --seed-url https://files.example.com/seed.iso

When --seed-url is set, between steps 6 and 7 above the CLI:

The seed ISO should have one of two volume labels (build it locally with isard seed cidata|oemdrv — see seed):

Both labels are auto-discovered by the install target — no kernel command line tweaks needed, and IsardVDI’s hardware schema has no extra-kernel-args field anyway.

Ready-to-edit starter configs (Ubuntu autoinstall + Fedora kickstart) live under examples/seed/. End-to-end runbook with expected cloud-init / Anaconda checks is at docs/phase3-verification.md.

All defaults are constants at the top of src/cli.rs — to expose them as flags again, add fields back to TemplateCreateArgs and pass them through to NewFromMediaRequest.

template promote

isard template promote <DESKTOP> [-n <NAME>] [-d <DESCRIPTION>] [--disabled]

Promote a stopped desktop into a reusable IsardVDI template via POST /api/v3/template. Resolves the desktop by name (fuzzy match through resolve_desktop), so you can pass any unique prefix.

Pre-conditions enforced server-side: the source desktop must be in Stopped state AND its storage must be ready (i.e. the disk must have actually been written — a desktop created via template create --seed-url ... and then never booted is not a valid promote source; you’ll get an HTTP 500 from upstream’s storage_ready check). The CLI prints a warning if desktop.state isn’t Stopped but leaves the call to the server, since IsardVDI may add or relax checks over time.

media

Subcommand group. With no action it defaults to list.

media list

isard media [--filter <SUBSTRING>]
isard media list [--filter <SUBSTRING>]

Calls IsardVdiClient::get_media, which merges /api/v3/media (owned) and /api/v3/media_allowed (shared) and dedupes by id. Prints a four-column table (NAME / STATUS / SIZE / OWNER) where OWNER is you or shared.

media add

isard media add <NAME> --url <HTTPS_URL>
                [-d|--description <TEXT>]
                [--no-wait]
                [--timeout <SECONDS>]   # default 1800

media delete

isard media delete <NAME-OR-ID> [-y|--yes]

seed

Build a local cloud-init / kickstart seed ISO. Pure local operation — no auth, no IsardVDI API call. The resulting ISO has to be hosted at an HTTPS URL (e.g. on your own webserver) and then registered via media add --url, because IsardVDI ingests media by URL only (there is no multipart upload route).

The seed ISO carries unattended-install configuration that the booting guest auto-discovers from the volume label:

SubcommandLabelFile on ISOPicked up by
cidataCIDATAuser-data + meta-datacloud-init NoCloud datasource (Ubuntu autoinstall, Fedora Cloud Base, etc.)
oemdrvOEMDRVks.cfgAnaconda (Fedora / RHEL / AlmaLinux / Rocky installer)

ISO generation is pure Rust via the hadris-iso crate — no xorriso / genisoimage / mkisofs binary needed on the host.

seed cidata

isard seed cidata --user-data <FILE> [--meta-data <FILE>] -o <OUTPUT.iso>

Typical Ubuntu autoinstall snippet:

#cloud-config
autoinstall:
  version: 1
  identity:
    hostname: ubuntu-template
    username: alice
    password: "$6$..."   # mkpasswd -m sha-512
  ssh:
    install-server: true
    authorized-keys:
      - ssh-ed25519 AAAA... me@laptop
  packages: [vim, htop]

seed oemdrv

isard seed oemdrv --kickstart <FILE> -o <OUTPUT.iso>

Anaconda automatically picks up ks.cfg from any volume labelled OEMDRV without needing inst.ks= on the kernel command line — handy because IsardVDI’s hardware schema has no extra-kernel-args field.

seed publish

isard seed publish --kind <oemdrv|cidata> \
    --hostname <NAME> --username <USER> \
    [--password <PW> | --password-stdin] \
    [--ssh-key <KEY> | --ssh-key-file <FILE>] \
    [--locale <L> --keyboard <K> --timezone <TZ>] \
    [--server <URL>] [--token <TOKEN>] [--json]

POSTs form fields to the /api/seed endpoint hosted on isard.xtec.dev (or a self-hosted copy via --server) and prints the resulting https://…/seeds/seed-<hex>.iso URL on stdout. Same operation as the web form — server renders the kickstart / cloud-init template, SHA-512-crypts the password, writes the ISO under /home/deploy/isard/seeds/, and returns the URL.

Idempotent: re-submitting the same fields returns the same URL and does not duplicate the file on disk.

Compose with template create:

URL=$(printf '%s' "$PASSWORD" | isard seed publish \
    --kind oemdrv \
    --hostname fedora-template \
    --username fedora \
    --password-stdin)
isard template create --seed-url "$URL"

End-to-end workflow (manual today)

  1. isard seed cidata --user-data my.yaml -o seed.iso (or seed oemdrv).
  2. Host seed.iso at an HTTPS URL your IsardVDI deployment can reach. Each user-data file may carry SSH keys or hashed passwords, so prefer unguessable paths and short-lived hosting over a public web root.
  3. isard media add my-seed --url https://your-host/seed.iso.
  4. Attach both installer ISO and seed ISO when creating the template. Multi-ISO attach via hardware.isos is structurally supported by the API (schemas/snippets/hardware.yml defines isos as an unbounded list) but is not yet wired into isard template create — that’s Phase 2 (see TODO.md).

create

isard create <NAME>
             [-t|--template <NAME-OR-ID>]
             [-d|--description <TEXT>]
             [-c|--category <UUID>]
             [--filter <SUBSTRING>]

Creates a desktop from an existing template via POST /api/v3/persistent_desktop. Distinct from template create, which provisions a new template by downloading and installing an OS.

Selection priority for --template (same as resolve_desktop): case-insensitive id → case-insensitive exact name → fuzzy/substring ranker. A typo like fedoa-server still resolves to fedora-server-42 when it’s the only candidate; when two or more templates match, isard create opens a dialoguer::Select picker showing name [os] so the user disambiguates explicitly. Non-TTY contexts fail loudly with the candidate list instead of guessing. If --template is omitted entirely, the existing interactive numbered list is shown (optionally filtered by --filter or --category).

Hardware defaults (set inside IsardVdiClient::create_desktop): 2 vCPU, 4 GB RAM, boot_order=["disk"], disk_bus="default", interfaces=["default","wireguard"], videos=["default"].

start <NAME>

isard start <NAME>
            [-w|--wait]
            [--timeout <SECS>]          # default 120
            [-b|--book]
            [--book-minutes <MINUTES>]  # default 60

stop <NAME>

isard stop <NAME> [-w|--wait] [--timeout <SECS>]

Calls GET /api/v3/desktop/stop/{id}; --wait polls for state=="Stopped".

delete <NAME>

isard delete <NAME> [-y|--yes] [--permanent]

view <NAME>

isard view <NAME>
           [--install / --no-install]   # default true
           [--debug]

ssh <NAME>

isard ssh <NAME>
          [-l|--user <USER>]
          [-i|--identity <PATH>]
          [--password <PASS>]
          [--timeout <SECS>]            # default 120
          [--dry-run]

Desktop name resolution

resolve_desktop in cli.rs, used by start, stop, delete, view, ssh:

  1. Exact match (case-insensitive) → returned silently. No prompt.
  2. Otherwise collect every desktop with strsim::jaro_winkler ≥ 0.5 OR a case-insensitive substring hit (substring hits get a baseline score of 0.5 so a short slice like web still surfaces against fedora-web-1). The list is sorted by score descending.
  3. One candidate → returned silently; cmd_view / cmd_ssh print the “Matched: ‘X’” confirmation line.
  4. Two or more candidatesdialoguer::Select picker labelled name (state). The highest-scoring candidate is pre-highlighted, so hitting Enter accepts the old silent-best-match behaviour with one extra keystroke. Non-TTY contexts (pipes / scripts) error instead of guessing: '<needle>' matches more than one desktop (…). Be more specific.
  5. Zero candidates → error listing every desktop name.

The same shape applies to cmd_create --template <name> (resolve_template), with labels of name [os].