Readymade Stack
Taidan

Taidan

Taidan (opens in a new tab) is the new out-of-box experience (OOBE) (opens in a new tab) application for Ultramarine Linux 41 and above. It is written in Rust and libhelium (opens in a new tab).

It is a replacement to rhinstaller initial-setup (opens in a new tab). This means that you may choose to use RDMS components except Taidan, or you may choose to use non-RDMS components but use Taidan to replace initial-setup. Components in RDMS are, in fact, components, and you (as the developer) have the freedom to choose to ship whatever components you prefer over rhinstaller (or other alternatives).

History

Driver installations were previously handled by umstellar (opens in a new tab), "a quick-and-dirty GUI post-install menu […] meant to only be used for Ultramarine Linux 39's Anaconda post-install menu"1. However, due to a misconfiguration, initial-setup was never enabled in the final UM39/UM40 images, deeming the effort pretty much useless — except one part where stellar would be run in a special mode that checks for designated criteria and installs NVIDIA and broadcom drivers as accordingly, but only during the Anaconda installation under the presence of an internet connection.

We realized there is a need to create a new OOBE application alongside an installer, which is why we started to work on Taidan in late Oct, 2024. The development cycle spanned less than 3 months, and the app was finished in early Jan, 2025.

Configurations

Taidan isn't just an app with a software catalogue (i.e. the app list), it comes with functionalities you would normally expect from an OOBE application:

  • user setup
  • theming
  • keymap and input method
  • etc.

Most of the above are universal across distributions and there are no configurations needed to change the behaviours of the above sections (at least we believe so).

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

Taidan also reads the /etc/com.fyralabs.Taidan/catalogue/ folder for the software catalogue. This may be changed by the runtime envvar TAIDAN_CATALOGUE_DIR. The syntax for the yml catalogue files are documented below.

In order to gather a list of keymaps, /usr/share/X11/xkb/rules/evdev.lst is read during compile-time.

In addition, the language list is also generated during compile-time.

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 customisations of tweaks. For the most up-to-date documentation, visit the source file at src/backend/tweaks.rs.

Tweaks are distro-specific settings read during runtime, which 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

The distribution SHOULD provide the translation texts, see Runtime Fluent Loader.

If either one of the above fields are not provided, or 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

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 yml 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 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 is a requirement to explicitly write out all possible choice combinations. However, 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/. You will know why in a minute.

Screenshots are similarly stored in data/screenshots/.

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

Similarly, the /com/fyralabs/Taidan/icons/symbolic/actions/ gresource path must contains 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)".