Skip to content

Rodeo Builder

→ Open the Rodeo Builder

The Rodeo Builder composes a new rodeo in the browser: pick the lab engine, put story chapters in order, tag their text for translation and story variants, and download the result as a profile you install with rodeo.

It comes in two versions of the same page:

  • Published (the button above): a static page built by GitHub Actions with the docs. Nothing you do there leaves your browser; you download a zip.
  • Live, installed with rodeo-cli: rodeo builder serves the page on your machine, reads its data live and adds Save to profiles, which writes the rodeo straight into ~/.rodeo/profiles/<name>/.
rodeo builder                 # opens http://127.0.0.1:8678/
rodeo up --profile <name>     # after Save to profiles

What you compose

  • Lab engine. suse-virt, rancher and suse-edge are rodeo's own engines; the (i) on each shows its size and what it provides (for example Harvester, KubeVirt, Longhorn, Rancher, Fleet). lab-in-a-box builds any lab lab-in-a-box can:
    • Open in Lab Builder opens lab-in-a-box's lab-builder with this rodeo's add-ons filled in. Download the lab.json there and upload it here to bring its add-ons back.
    • Embed here opens the lab-builder inside the page. Handing the lab back directly needs a lab-in-a-box release with the embed hand-off; until then, use Download there and Upload here.
    • Import existing lab starts from a bundled lab-in-a-box rodeo (smlm-workshop) or from a lab.json.
  • Chapters. The library lists every workshop's chapters: the bundled examples' story/ directories, the workshops whose chapters live in their own repository (listed in rodeo/builder/chapter_sources.yaml: the SMLM workshop's Instruqt track and the Virtualization workshop's exercises), the story/ of every published rodeo in the catalog (rodeo/data/catalog.d/, unless chapter_sources.yaml already names its repository), and the chapters you create with + New. Workshops start collapsed: click a workshop's title to show its chapters. The (i) next to it (hover, or click to keep it open) shows where the chapters come from, the lab engine and profile they run on, their count and time, and what they need. use sets the rodeo up as that workshop: its lab engine and profile (for example suse-virt with the virt-workshop-aws profile, whose extra setup the exercises need) and all its chapters, in place of the rodeo's (Undo restores them); + all adds its chapters to the rodeo's. A chapter card shows its title; hover over it for its time and needs, or turn on the list button next to the search to show them on every card. The funnel button next to it shows only the workshops for this lab engine, the chapters this lab can run, or the chapters not in the rodeo yet. Drag chapters into the rodeo (anywhere on the list) or click them; click a chapter again to take it out. Click a row in the rodeo to insert new chapters after it instead of at the end. Reorder by dragging or with Alt+↑/↓. Removing a chapter or a span offers Undo for a few seconds. When every chapter comes from workshops that run on one other engine, a banner offers to switch to it. A chapter that needs something the lab doesn't provide is marked, and for lab-in-a-box one click adds the missing add-on. Chapters that come with a check script bring it along. A new chapter's Requires from the lab field suggests, as you type, what an engine really provides: the native engines' capabilities and lab-in-a-box's catalogue (add-ons, infrastructure services, Kubernetes cluster types). If the lab-in-a-box catalogue could not be read, the page says why. When nothing matches, Enter adds what you typed as a new need: a placeholder for something no engine provides yet, like missing_addon. Such a need only marks the chapter as "work needed" (also listed, by name, in the download's README); it never blocks the rodeo and never reaches the lab.
  • Import. Import above the library adds a workshop from a URL: a git repository or a directory in one (https://<host>/<owner>/<repo>/tree/<ref>/<dir>). The builder detects the format (a rodeo story/, an Instruqt track <dir>/*/assignment.md, or markdown <dir>/*.md) and lists its chapters as a new group; × removes the group again. The published page reads public github.com repositories through the GitHub API (60 unauthenticated requests an hour); the rodeo builder server clones any public https repository (shallow and sparse, no credentials). Imported groups are kept with the draft.
  • Story. Edit on a chapter opens the story editor (rows with span warnings show how many). Select text and click Tag selection, use the toolbar, or press Alt+T / Alt+I / Alt+N to mark it Translatable, Invariant or No-lang; double-click a span to change its type, language, id or story variant. Ids follow rmstory's rules (<chapter>.N, <parent>.N when nested) and the editor shows the same warnings as rmstory validate. The source and the preview sit side by side and scroll together; the preview renders the chapter as Markdown or HTML (only safe tags, images as links), with its spans marked. Ctrl+Z / Ctrl+Shift+Z undo and redo; Cancel closes the editor and discards its changes, Done keeps them.
  • Translations. The story editor's translations tab lists every translatable span per language. Click a cell to write or review a translation; a nested span stays in it as <span id="..."/>. The download carries them in rmstory's translation store, story/strings/, and rodeo story render --language <lang> uses them (with --engine for the spans still missing).

Review and download (step 03) checks the rodeo before you download it: the lab covers every chapter, the span ids are valid (each warning has a Fix button), the exact files in the zip (listed when you click N files in .zip), and the commands to install it, ready to copy. Missing lab features and span warnings never block the download.

The page keeps a draft of your rodeo in this browser. After a reload it offers to restore it.

With rmstory on the live builder

When rmstory is installed where rodeo builder runs (sudo rodeo install-deps --story), the live page also uses it:

  • Translate missing (translations tab): fills the chosen language's missing translations with an rmstory engine (rmstory extract --engine <e> --to <lang>). They are stored as draft; what you wrote or reviewed is kept. rmstory also gives unnamed spans an id (<chapter>.N).
  • Validate (step 03): rmstory's own validate on the chapters.
  • Draft with an engine (New chapter): an engine that can generate text (rmstory engines says "translate + generate", for example claude-code, ollama or gemini) drafts the chapter from your description, told what the lab provides and which chapters come before it. Its spans are renumbered new-chapter.N; review the draft before you create the chapter.
  • New story (rodeo tab): the same exercises told as a different story. You say what is what with the span tags (story editor):
  • story: spans with a story variant (hist): retold;
  • exercise: translatable spans without a story variant: translated, never retold;
  • fixed: invariant (no) or no-lang spans, and text outside spans: kept word for word.

Say what the new story is about and pick an engine that can generate text; rmstory generate rewrite --theme rewrites the story spans and the exercise spans never reach the engine. The retold chapters (ids <chapter>-<theme>, story span ids <theme>.<id>; exercise and fixed spans keep their ids and translations) become a library group and replace the rodeo's chapters; Undo puts the originals back.

Engines read their keys from the environment rodeo builder runs in, never from the page: for example DEEPL_AUTH_KEY, GEMINI_API_KEY, RMSTORY_OLLAMA_URL, or a logged-in claude command for claude-code. The published page writes translations by hand only.

The right-hand panel sets the name, story title, language, deployment target and story variant, and previews every file of the download. plan.yaml is an editor: change anything and the download keeps it; name, deployment_target and story stay in sync with the rodeo tab, and Reset to generated drops your edits. Changing the lab engine regenerates the plan, after asking.

Infrastructure and instructions

The page keeps the lab (Infrastructure: engine, base profile, plan, definition, lab-in-a-box lab) apart from what students read (Instructions: chapters, story variants, translations, checks). Step 03 downloads either the whole rodeo or one part: Infrastructure only (<name>-infrastructure.zip, installable with rodeo new --from-zip) or Instructions only (<name>-instructions.zip: story/ and checks/, laid out to be pushed as a content repository).

Instructions repository (rodeo tab) points the plan at instructions kept in their own repository: the URL (https://<host>/<owner>/<repo>[/tree/<ref>/<path>]) and format become the plan's content: block. rodeo up renders them after the lab is up, and rodeo content apply updates them on a running lab without redeploying (see the plan reference).

Install what you downloaded

The zip holds one profile directory, <name>/, with rodeo-plan.yaml, the chapters (story/NN-<id>.md), story/chapters.yaml (minutes, needs and check script per chapter), one story/stories/<variant>.yaml per story variant, checks/check-<id>.sh stubs, a README with the exact commands and, when the rodeo starts from a profile (suse-virt, rancher, suse-edge or an imported lab-in-a-box rodeo), builder.yaml naming that base profile.

rodeo new <name> --from-zip <name>.zip
rodeo up --profile <name>

--from-zip copies the base profile, lays the zip's files over it and writes ~/.rodeo/profiles/<name>/. A new lab-in-a-box rodeo carries its own definition.yaml and needs no base. --from <profile> overrides the base and --force replaces an existing profile. The zip may only hold the files the builder writes; anything else (other paths, links, oversize files) is refused before anything is written. Save to profiles in rodeo builder does the same without the zip.

A published rodeo (see Publish to GitHub) installs the same way from its repository, and a catalog rodeo by its name alone:

rodeo new <name> --from https://github.com/<owner>/<repo>[@<ref>]
rodeo up --profile <catalog name>     # fetched and installed on first use
rodeo profiles                        # lists the catalog too

The repository is fetched shallow, without credentials, and only the files a builder rodeo may hold are installed; ~/.rodeo/profiles/<name>/rodeo.lock records the repository, ref and commit. --force re-fetches.

The live builder

rodeo builder serves rodeo/builder/htdocs/ and answers the page's requests with rodeo/builder/api.py (rodeo/builder/server.py, standard library only). It links to and embeds lab-in-a-box's published static lab-builder, or the one --lab-builder-url names. With --start-lab-builder it starts the checkout's own dynamic lab-builder (webui/run-local.py) next to it on --lab-builder-port, opened on the host name the browser used for the builder (the published one when the checkout has none or it can't start). That lab-builder has no login or token, so any site you browse can save labs through it: it is off by default and only allowed on a loopback --host. At start it fetches the chapter sources (a source that can't be fetched is left out, with a warning) and the lab-in-a-box catalogue: --labinabox <checkout>, else RODEO_LABINABOX_PATH, else lab-in-a-box's latest release, fetched into ~/.rodeo/vendor/lab-in-a-box/ the way a deploy does.

Option Default
--host, --port 127.0.0.1, 8678 Where to listen (--port 0 picks a free port).
--expose off Required for a non-loopback --host: anyone who reaches it can save profiles as you. Publish then uses the github.com pages, never this host's GitHub login.
--allow-host NAME Extra Host name to accept (the name or IP clients use with --expose).
--labinabox latest release lab-in-a-box checkout for the catalogue and the lab-builder.
--lab-builder-url URL (RODEO_LAB_BUILDER_URL) the published lab-builder A lab-builder you already run (for example on an automation node) for the page to link to and embed; nothing is started.
--start-lab-builder off Start the checkout's own dynamic lab-builder. Loopback --host only.
--lab-builder-port 8677 Port of the lab-builder --start-lab-builder starts, on the same address as --host.
--source ID=CHECKOUT, --no-fetch-sources fetch all Local chapter sources, or none.
--open/--no-open open Open the page in a browser.

Writes are guarded: the server only answers requests whose Host is an allowed name (loopback names by default), so other sites can't reach it through DNS rebinding, and Save sends a per-run token that only the served page has. The page runs under a Content-Security-Policy that allows only its own files (the fonts ship in htdocs/fonts/) and the lab-builder frame.

Deploy without saving

Deploy (header) runs the rodeo on the builder's host exactly as it is on the page, without saving it to your profiles first. It needs rodeo builder on loopback (not --expose) and root for rodeo up: the builder runs as root or the user has passwordless sudo (sudo -n true). Otherwise, and on the published page, the button is greyed out and says why on hover.

  • The dialog says plainly that the rodeo is not saved, and offers Save to profiles and deploy next to Deploy without saving.
  • The deploy runs rodeo up --yes --no-tmux --dir <lab> from a copy in ~/rodeo-labs/<name>-unsaved-<UTC time>/ (base profile plus the page's files, as Save lays them out), its output in <lab>/.builder-deploy.log. Secrets rodeo can generate go to the lab's .rodeo-secrets.yaml; ones only you have (operator_secrets) are asked for in the dialog and written to ~/.rodeo/secrets.yaml, never shown again.
  • A banner stays while it runs and after it ends, with the lab directory, the log, Stop (interrupts rodeo up like Ctrl-C) and Save to profiles, and keeps saying the rodeo is not saved until you save it. One deploy runs at a time; rerun or resume from the lab directory with rodeo up.

Settings and stored credentials

Settings, in the user menu (the person icon at the right of the header, which also shows the user@host the builder runs as), keeps the credentials you deploy with — SCC registration codes, registry and Application Collection logins, AWS keys, SSH keys, GitHub tokens, or any key: value fields — encrypted on the builder's host, one file per credential in ~/.rodeo/vault/. It works only on rodeo builder on loopback: with --expose and on the published page, Settings is greyed out in the menu with the reason.

  • Each credential has its own password, at least 8 characters, asked every time the credential is used. Neither the password nor the values are kept by the page: the credentials list shows a credential's name, type, when it was added and last used, never its values.
  • In the deploy dialog, each secret the lab asks for offers Use stored… with the credentials that can fill it. After the password, the page fills that secret and every other one the same credential provides (an AWS credential fills both aws_access_key_id and aws_secret_access_key). A generic credential fills secrets whose names match its field names.
  • The filled values go where typed ones go: into ~/.rodeo/secrets.yaml for rodeo up. Deleting a stored credential does not remove them from there.
  • The dialog also lists the keys already in ~/.rodeo/secrets.yaml, names only and read-only: rodeo up reads that file directly. In the deploy dialog, Use stored… offers them under secrets.yaml (names sharing a word with the secret first). Picking one asks no password and locks the input ("from secrets.yaml: ", Clear to type instead); the builder copies the value on its host, so it never reaches the browser.

Publish to GitHub

Publish puts the rodeo in a public GitHub repository of yours and opens a pull request on rodeo-cli that adds it to the rodeo catalog (rodeo/data/catalog.d/<name>.yaml: name, title, repository, ref, engine, languages). The repository holds the files of the download.

  • rodeo builder on loopback uses the GitHub login already on its host, without storing it: GH_TOKEN or GITHUB_TOKEN, else the GitHub CLI's login (gh auth token), else git's stored github.com credential. With none, Log in to GitHub runs gh auth login --web and shows its one-time code to enter at github.com/login/device; without gh, install it, run gh auth login or set GH_TOKEN before rodeo builder. Publishing creates github.com/<you>/<name>, pushes one commit on main, forks rodeo-cli when you don't own it and opens the catalog pull request. Update my existing repository pushes a new commit that replaces the files and opens no pull request. RODEO_CATALOG_REPO points the pull request at another repository.
  • The published page (and rodeo builder --expose) links to github.com pages that use your browser's GitHub login, and GitHub asks you to log in when you aren't: create the repository, upload the unzipped download (or git push), and add the catalog file, which forks rodeo-cli and opens the pull request under your account.

How the published page is built

The published page is the live page's static subset. scripts/build-builder-static.py embeds what rodeo/builder/api.py answers (engines and bundled chapters from rodeo/data/, the chapter sources' chapters, and the catalogue of lab-in-a-box's latest release) into the page and marks it static, which hides Save to profiles; the docs workflow publishes it at builder/. The build fetches each chapter source with a shallow, sparse git clone (only the chapter files and check scripts) and fails if one can't be fetched, or if the lab-in-a-box catalogue can't be read. Build it locally:

python3 scripts/build-builder-static.py --output /tmp/builder --labinabox /path/to/lab-in-a-box
python3 -m http.server -d /tmp/builder 8000

--source <id>=<checkout> uses a local checkout for one chapter source, --no-fetch-sources builds without them (offline), and --no-labinabox builds without the lab-in-a-box catalogue (the page then shows it as unavailable).