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
(aliasname
): the fluent ID for the name of the tweak- fallback:
<id>-name
- fallback:
ftl_desc
(aliasdesc
): the fluent ID for the description of the tweak- fallback:
<id>-desc
- fallback:
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
-
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)". ↩