This is another post in the series: “A Personal Multi Installer in Lua”.
- 2) Proof of Concept for a “Multi” Installer <- this post
- 1) Motivations and Ideas for a “Multi” Installer
This post describes the current Work in Progress with screenshots and previews. Please refer to the previous post for more background information.
The idea is that a client creates an installation file, and a server performs the installation. A proof of feasibility is available:
- installer.lua reads the configuration file and builds the start menu.
- installer.lua calls client.lua.
- client.lua uses the configuration file and the selected frontend (currently TUI and CLI) to create the installation file.
- installer.lua calls server.lua
- server.lua reads the installation file and actually performs the installation on the machine.
The installer is independent of any graphical interface. Both the client and the server use a generic “Frontend” object, and they are unaware of its specific implementation. The installer can also run without a user interface:
- The client can create the installation file by reading the default values from the configuration file or through auto installation.
- The server can execute the installation commands without a user interface.
That is a non-interactive installation. The installer provides several options for interactive, semi-interactive, and non-interactive installations, which will be described later in this post.
The installation file acts as the communication file between the client and server. Additionally, it offers two useful features:
- It can be reused.
- It can function as a standalone installer.
More details will follow.
The installer skeleton is complete and serves as a proof of concept. It meets most of the requirements and features outlined in the first post: multi-language, multi-frontend, multi-way, and multi-interactivity.
Configuration File
The installer starts by reading the configuration file, which allows you to fully customize the installer settings. It also includes options from bsdinstall. Here is a snippet:
------------------------------------------------------------
--- Starting configuration, you can set up the installer ---
-- Clone bsdinstall OSNAME
Config.OSNAME = "FreeBSD"
-- Clone bsdinstall EFI_LABEL_NAME
Config.EFI_LABEL_NAME = "FreeBSD"
-- Logging and debug
Config.LOGGING_FILE = "/tmp/install.log"
Config.LOGGING_LEVEL = 3
-- Start menu beep: at start up and after restart (accessibility)
-- Config.BEEP = true
-- Client creates Config.INSTALLATION from this configuration file and the
-- user interface. The server reads it to perform the installation on the
-- machine.
-- If Config.CLIENT=false this should be an existing file for the server.
Config.INSTALLATION = "/tmp/installation.lua"
-- Enable client. If disabled the installer will run only the server, so be sure
-- Config.INSTALLATION already exists.
Config.CLIENT = true
-- Enable server
Config.SERVER = true
-- Directory to store "etc" files during server installation,
-- clone bsdinstall BSDINSTALL_TMPETC
Config.TMP_ETC = "/tmp"
.....
--------------------------------------------------------------------------------
-- Screens to show to the user. It is possible to set default values for some
-- screens. If the screen is disabled but the default value is set, it will be
-- written in the INSTALLATION file.
-- Start Menu
Config.SCREEN_STARTMENU = true
-- Hostname
Config.SCREEN_HOSTNAME = true
Config.SCREEN_HOSTNAME_VALUE = HOSTNAME
.....
Then, the installer builds the Start Menu. Three beeps sound to inform blind and low-vision users that they can start the installation (more on this later).
Expert Menu
The Expert Menu (Start Menu > Advanced --->) offers various features. For example”
“File Installation” allows you to select an installation file. The file can be manually created, generated from a previous installation, or generated from the configuration file. If the file exists, the installer skips the client step and directly starts the server. Of course the client can be disabled via the configuration file, to run a semi- or non-interactive installation by reading an installation file.
“Logging” lets you choose a debugging/logging level.
Just a log snippet:
====================================================
= Starting Installer Sat Mar 15 14:17:49 2025
====================================================
DEBUG: Loaded configuration file: "config.lua"
DEBUG: load "./i18n.lua"
DEBUG: load "./configure.lua"
DEBUG: load "./startmenu.lua"
DEBUG: load "./client/client.lua"
DEBUG: load "./userinterface/frontend.lua"
DEBUG: load "./client/hostname.lua"
DEBUG: load "./server.lua"
DEBUG: load "./userinterface/cli.lua"
DEBUG: call ./userinterface/frontend.lua:16 new()
(arg) self = object table: 0xad707088880
(arg) o = nil
DEBUG: load "./userinterface/gui.lua"
DEBUG: load "./userinterface/tui.lua"
DEBUG: call ./userinterface/frontend.lua:16 new()
(arg) self = object table: 0xad707088880
(arg) o = {
__tostring = "TUI",
}
DEBUG: load "./userinterface/web.lua"
DEBUG: call ./configure.lua:17 newInstallationTable()
DEBUG: call ./userinterface/tui.lua:146 new()
(arg) self = object Frontend
DEBUG: call ./startmenu.lua:171 run()
.....
====================================================
= Start Client
====================================================
DEBUG: call ./client/client.lua:49 loop()
(arg) ui = {
__tostring = "TUI",
new = function,
menu = function,
radiolist = function,
inputString = function,
hostname = function,
highContrast = function,
message = function,
question = function,
}
.....
====================================================
= Start Server
====================================================
...
Reading Installation file "installation.lua"
DEBUG: load "installation.lua"
Read Installation file "installation.lua"
DEBUG: call ./server.lua:8 shell()
(arg) cmd = "BSDINSTALL_TMPETC=/tmp HOSTNAME=laptop.bsd
./bsdinstall/hostname"
----- Command ----
BSDINSTALL_TMPETC=/tmp HOSTNAME=laptop.bsd ./bsdinstall/hostname
1>/tmp/lua_u0OmXk 2>/tmp/lua_QeM98G
rv: 0
out: err:
-----------------
.....
---------------- Dialog -------------------------
bsddialog --backtitle 'Installing FreeBSD 15.0-CURRENT'
--title ' Complete ' --no-label 'Shell'
--ok-label 'Reboot' --extra-button --extra-label 'Shutdown'
--yesno 'Installation Complete. Thank you to use FreeBSD!'
0 0 2>/tmp/lua_VZ6R
Button: Shutdown (dialog rv:3)
stderr:
------------------------------------------------
====================================================
= End Installer Sat Mar 15 14:18:19 2025 ====================================================
Main Menu
Returning to the Start Menu.
You can switch to “Dark Theme” for improved visibility.
The installer supports multiple frontends and can seamlessly switch to CLI mode using a simple key combination (“S + Enter”) to ensure screen reader compatibility. Special thanks to the FreeBSD Foundation for making this possible. It’s important to note that this is currently just a draft and will be improved further.
The i18n (internationalization) module is implemented, allowing language selection through the “Language” option.
If a translation is unavailable, the default English text is used. To note “Beep” “Disabled”.
Client Loop
The client provides its features in a loop, so it is easy to implement:
- Multi-way installation
- Navigation buttons
After selecting “Install”, choose between Auto
, Simple
, or Expert
installation modes.
Each screen includes navigation buttons: [Prev]
, [Restart]
, and [Next]
.
After selecting [Restart]
, you can choose between Soft
, Hard
, or Reboot
.
New guideline: each screen has to provide an help (F1
key).
Server
The server prompts for confirmation before reading the configuration file and proceeding with the OS installation.
If an error occurs, it displays the failed command along with the return value, stdout, and stderr.
In verbose mode, the executed command is also displayed; please see Expert Menu above.
Multi interactive
The configuration file offers extensive flexibility to customize user interactivity levels: interactive, semi-interactive, and non-interactive.
The setup is the combination of Start Menu (Enable/Disabled) + Client UI (Enabled/Disabled (also for each screen)) + Client (Enabled/Disabled) + Server UI and Verbose (Enabled/Disabled).
Fully Interactive (user driven) Installation example:
- Start Menu enabled + client enabled + client UI enabled + server UI and Verbose enabled
Fully Non-Interactive (no user interaction) Installation examples:
- Client UI disabled (client generates the installation file from default values or auto mode) + server UI and Verbose disabled
- Alternatively, if the installation file already exists: the client disabled, the installer directly passes the installation file to the server with UI and Verbose disabled.
Standalone Installer
The installation file is both a client/server communication file and a standalone installer. Here is a snippet:
--- Installation file created on Sat Mar 15 00:25:54 2025 ---
-- Uncomment the following line to use this file like a standalone installer.
-- Example, run: '# lua /root/installation.lua'.
-- local standaloneInstaller = true
local installation = {
version = "15.0-CURRENT",
[1] = {
label = "hostname",
command = "BSDINSTALL_TMPETC=/tmp HOSTNAME=fbsd.mtc ./bsdinstall/hostname"
}, -- End hostname
[2] = {
label = "date",
command = {
.....
} -- End Installation
local function install()
for _, step in ipairs(installation) do
if type(step.command) == "table" then
for _, c in ipairs(step.command) do
os.execute(c)
end
else
os.execute(step.command)
end
end
end
if standaloneInstaller then install() end
.....
To use the file as a standalone installer, uncomment the line:
-- Uncomment the following line to use this file like a standalone installer.
-- Example, run: '# lua /root/installation.lua'.
local standaloneInstaller = true
And execute:
# lua /root/installation.lua
WIP and Future
Currently finishing reading Programming in Lua, 4th Edition.
Integrating bsdinstall components and adding progress bars to the server:
Currently, server.lua simply loads the installation file and executes its commands, adding only logging and a user interface. In pseudocode:
--Server.lua
installation = load("installation file")
for step in (installation)
execute(step.command)
(step.namen step.command)
logUI.window(step.name, step.comman)
end
I am considering merging server.lua with the installation file so it becames a simple sequence of commands.
The server uses a lightweight version of the bsdinstall components, without a user interface and logging. For example, the hostname script becomes:
# Default file to store hostname entry in
: ${HOSTNAMEFILE:=$BSDINSTALL_TMPETC/rc.conf.hostname}
# Store the user's choice
echo hostname=\"$HOSTNAME\" > $HOSTNAMEFILE
retval=$?
# Activate entry if configured
if [ "$BSDINSTALL_CONFIGCURRENT" ]; then
hostname -s $HOSTNAME
retval=$?
fi
exit $retval
I am also considering rewriting the shell components in Lua.
Adding a final step to install a desktop environment, ports, and packages for accessibility. For example:
I am considering to integrate this project to provide the web frontend.
Finally, I am working on improvements and extensions for the partitioner. Several feature requests exist, primarily on Bugzilla.