Readymade Stack
Taidan

Taidan

Taidan (opens in a new tab) is the out-of-box experience (opens in a new tab) for Ultramarine 42 and newer. It's built with Rust and libhelium (opens in a new tab).

Taidan is standalone from Readymade, meaning you can combine it with any installer, or even lack thereof (in the case of preinstalled images.)

History

Driver installation was previously handled by Stellar (opens in a new tab), "a quick-and-dirty GUI post-install menu […] meant to be used for Ultramarine Linux 39's Anaconda post-install menu"1.

We messed this up, and ended up repurposing Stellar to install Nvidia and Broadcom drivers if you connected to the internet during installation.

We decided pretty early in Readymade's design that the system should be configured on first-boot rather than in the installer, leading to us building Taidan. Development spanned less than 3 months from October '24 to January '25.

Configuration

Taidan handles a few important tasks:

  • System language
  • Keyboard layout and input method
  • User creation
⚠️

The list of keymaps and languages are generated at compile time!

Keymaps are read from /usr/share/X11/xkb/rules/evdev.lst at compile-time.

The basics should just work on most distributions! If you need more, Taidan can also offer:

  • Setting the theme and accent colour
  • Enabling scheduled screen tinting (night light)
  • System tweaks (distro-defined, e.g. swapping default Flatpaks for RPMs)
  • Provide a selection of apps for the user to pick from

Taidan generates part of the configurations from the /etc/os-release file. It requires the NAME= and VARIANT_ID= fields to be present, otherwise the app panics. This behavior will change in future versions to cater for other distributions.

Runtime Fluent Loader

Fluent is the only supported i18n framework. Distributions may choose to provide custom translations in /usr/share/taidan/po/. These translations are preferred over the compile-time ones.

Tweaks

Taidan allows distributions to present "tweaks", options to be presented to the user, in Ultramarine we use tweaks to offer RPMs over Flatpaks for those who prefer it.

Tweaks are read at runtime.

For the most up-to-date documentation, visit the source file at src/backend/tweaks.rs.

Tweaks MUST be stored in TWEAKS_DIR, i.e. /usr/share/taidan/tweaks. Here is an example of the file hierarchy:

/usr/share/taidan/tweaks/ (TWEAKS_DIR)
┣╸my_tweak/
┃ ├╴tweak.toml (optional)
┃ └╴up (required, MUST be executable)
┗╸other_tweak/
  ├╴tweak.toml
  └╴up

The ID of a tweak is determined by the directory name (my_tweak, other_tweak).

The tweak.toml file, if present, MUST specify the following two fields:

  • ftl_name (alias name): the fluent ID for the name of the tweak
    • fallback: <id>-name
  • ftl_desc (alias desc): the fluent ID for the description of the tweak
    • fallback: <id>-desc

Distributions SHOULD provide translations for their tweaks, see Runtime Fluent Loader.

If either of the fields are not provided, the TOML file cannot be parsed, or the file does not exist, Taidan will instead use the aforementioned fallback values.

The up executable will be given a single argument of either 1 or 0, denoting whether the user enables this tweak.

In addition, the Settings struct is serialized as JSON and fed to the executable via stdin.

Catalogue Customisations

Taidan reads the /etc/com.fyralabs.Taidan/catalogue/ folder for the software catalogue. This may be changed by the runtime envvar TAIDAN_CATALOGUE_DIR.

Here is an example of the catalogue yml configuration file (taken from (opens in a new tab) here (opens in a new tab)):

category: Browsers
icon: web-browser-symbolic
choices:
  - name: Firefox
    provider: Mozilla
    description: Firefox is an open source web browser using the Gecko engine. Firefox has historically been the default in Ultramarine.
    note: Some features like WebUSB may be unavailable.
    actions: ;
 
  - name: Edge
    provider: Microsoft
    description: Edge is a Chromium-based browser centered around the Microsoft ecosystem, including many convenient and AI features.
    options:
      - radio: [Edge Stable, Edge Dev, Edge Beta]
    actions:
      - shell:rpm --import https://packages.microsoft.com/keys/microsoft.asc;enable_yum_repo:https://packages.microsoft.com/yumrepos/edge/config.repo;rpm:microsoft-edge-stable
      - shell:rpm --import https://packages.microsoft.com/keys/microsoft.asc;enable_yum_repo:https://packages.microsoft.com/yumrepos/edge/config.repo;rpm:microsoft-edge-dev
      - shell:rpm --import https://packages.microsoft.com/keys/microsoft.asc;enable_yum_repo:https://packages.microsoft.com/yumrepos/edge/config.repo;rpm:microsoft-edge-beta
 
  - name: Chrome
    provider: Google
    description: Chrome is the world's most popular web browser and the base for many others.
    note: Chrome may say it is managed by an organization, this is because of Fedora's bookmarks package.
    options:
      - radio: [Chrome Stable, Chrome Dev, Chrome Beta]
    actions:
      - enable_yum_repo:google-chrome;rpm:google-chrome-stable
      - enable_yum_repo:google-chrome;rpm:google-chrome-unstable
      - enable_yum_repo:google-chrome;rpm:google-chrome-beta
 
  - name: Lutris
    provider: Lutris Team
    description: |
      Lutris helps you install and play video games from all eras and from most gaming systems. By leveraging and combining
      existing emulators, engine re-implementations and compatibility layers, it gives you a central interface to launch all your games.
    options:
      - checkbox: flatpak
    actions:
      - rpm:lutris
      - flatpak:net.lutris.Lutris

Each YAML file in the catalogue directory should specify the name of category:, gtk icon: id, and a list of choices:. Each choice should contain the name:, provider:, description: and actions:. editions:, note: and options: are optional fields. editions: must be a list of strings. The app will be shown to systems with a matching edition (we get this from the variant field in os-release) listed in this field.

An option listed in options: must either be a - radio: [...] or a - checkbox: ....

The following section uses the term "choices" to refer to user preferences on each app they would like to install, which is different from choices:.

Denote a list of options of length N as opts[N], such that for i in 0..N (excluding N), opts[i] must be a list of choices with length C. For radio buttons, the list is in the form of choices[C] = ["Edge Stable", "Edge Dev", "Edge Beta"], but for checkboxes, the list is in the form of choices[2] = [nil, "flatpak"] (see the Lutrix example). This means opts[][] is a 2-dimensional nested array of optional strings.

Let opts_lengths[N] be an array of integers, where for i in 0..N, opts_lengths[i] is the length (denoted as C for each choice list) of the list of choices[C] stored in opts[i].

Thus, actions: is an N-dimentional nested array (or it could be a unit value when N = 0) storing the corresponding action that should be done when the corresponding choices are selected.

Let's look at a very complicated example:

- name: Test
  provider: Meow
  description: Meow
  options:
    - checkbox: 1 # ← opt[0] = [nil, "1"]      │ opts_lengths[0] = 2
    - radio: [a, b, c] # ← opt[1] = ["a", "b", "c"] │ opts_lengths[1] = 3
    - radio: [A, B, C] # ← opt[2] = ["A", "B", "C"] │ opts_lengths[2] = 3
    - checkbox: meow # ← opt[3] = [nil, "meow"]   │ opts_lengths[3] = 2
  actions:
    - # when checkbox (opt[0]) not pressed
      - # when radio (opt[1]) has "a" selected
        - # when radio (opt[2]) has "A" selected
          - shell:echo "no select 1, select a, select A, no select meow"
          - shell:echo "no select 1, select a, select A, select meow"
        - # when radio (opt[2]) has "B" selected
          - shell:echo "no select 1, select a, select B, no select meow"
          - shell:echo "no select 1, select a, select B, select meow"
        - # when radio (opt[2]) has "C" selected
          - shell:echo "no select 1, select a, select C, no select meow"
          - shell:echo "no select 1, select a, select C, select meow"
      - # when radio (opt[1]) has "b" selected
        - # when radio (opt[2]) has "A" selected
          - shell:echo "no select 1, select b, select A, no select meow"
          - shell:echo "no select 1, select b, select A, select meow"
        - # when radio (opt[2]) has "B" selected
          - shell:echo "no select 1, select b, select B, no select meow"
          - shell:echo "no select 1, select b, select B, select meow"
        - # when radio (opt[2]) has "C" selected
          - shell:echo "no select 1, select b, select C, no select meow"
          - shell:echo "no select 1, select b, select C, select meow"
      - # when radio (opt[1]) has "c" selected
        - # when radio (opt[2]) has "A" selected
          - shell:echo "no select 1, select c, select A, no select meow"
          - shell:echo "no select 1, select c, select A, select meow"
        - # when radio (opt[2]) has "B" selected
          - shell:echo "no select 1, select c, select B, no select meow"
          - shell:echo "no select 1, select c, select B, select meow"
        - # when radio (opt[2]) has "C" selected
          - shell:echo "no select 1, select a, select C, no select meow"
          - shell:echo "no select 1, select a, select C, select meow"
    - # when checkbox (opt[0]) is pressed
      - # when radio (opt[1]) has "a" selected
        - # when radio (opt[2]) has "A" selected
          - shell:echo "select 1, select a, select A, no select meow"
          - shell:echo "select 1, select a, select A, select meow"
        - # when radio (opt[2]) has "B" selected
          - shell:echo "select 1, select a, select B, no select meow"
          - shell:echo "select 1, select a, select B, select meow"
        - # when radio (opt[2]) has "C" selected
          - shell:echo "select 1, select a, select C, no select meow"
          - shell:echo "select 1, select a, select C, select meow"
      - # when radio (opt[1]) has "b" selected
        - # when radio (opt[2]) has "A" selected
          - shell:echo "select 1, select b, select A, no select meow"
          - shell:echo "select 1, select b, select A, select meow"
        - # when radio (opt[2]) has "B" selected
          - shell:echo "select 1, select b, select B, no select meow"
          - shell:echo "select 1, select b, select B, select meow"
        - # when radio (opt[2]) has "C" selected
          - shell:echo "select 1, select b, select C, no select meow"
          - shell:echo "select 1, select b, select C, select meow"
      - # when radio (opt[1]) has "c" selected
        - # when radio (opt[2]) has "A" selected
          - shell:echo "select 1, select c, select A, no select meow"
          - shell:echo "select 1, select c, select A, select meow"
        - # when radio (opt[2]) has "B" selected
          - shell:echo "select 1, select c, select B, no select meow"
          - shell:echo "select 1, select c, select B, select meow"
        - # when radio (opt[2]) has "C" selected
          - shell:echo "select 1, select c, select C, no select meow"
          - shell:echo "select 1, select c, select C, select meow"
#     ↑ ↑ ↑ ↑
#     2 3 3 2
#     ┬ ┬ ┬ ┬
#     └╼┿━┿━┿━━ should appear 2 times
#       └╼┿━┿━━ should appear 3 times per above item, i.e. total of 2×3 = 6 times
#         └╼┿━━ should appear 3 times per above item, i.e. total of 2×3×3 = 18 times
#           └—— should appear 2 times per above item, i.e. total of 2×3×3×2 = 36 times

As of version 0.1.9, it's required to explicitly write out all possible choice combinations, this might change in the future.

Possible action values include copr:..., enable_yum_repo:..., rpm:..., shell:..., flatpak:..., or a semicolon (;)-separated list of the above values for running multiple actions, or todo.

Catalogue App Icons and Screenshots

App icons should be stored in data/catalogue/{category}/{app}.svg. The exact names are not important; in fact, you may store them in other places relative to data/ (we'll explain in a moment).

Screenshots are similarly stored in data/screenshots/.

The data/icons.gresource.xml must be modified such that the GResource path /com/fyralabs/Taidan/screenshots/ contains all screenshots with aliases in the format of ss-{category}-{app}.png, where {category} is the name of the YAML file excluding extensions, and {app} is in lowercase letters and spaces are replaced by - dashes.

Similarly, the /com/fyralabs/Taidan/icons/symbolic/actions/ GResource path must contain icons in the format of ctlg-{category}-{app}.svg.

In addition, you should include other icons you have chosen to use for the icon: field for the categories, in /com/fyralabs/Taidan/icons/symbolic/actions/.

Footnotes

  1. quote the readme file: "This script will be deprecated in the future when we implement our own OOBE (probably 41). For now, it's a stopgap in our transitional OOBE (Readymade install -> Anaconda/DE OOBE -> Stellar)".