Overview
Sometimes all of us need to make a graphical installer for one's own linux distro. It goes without saying that you are able to use a distro-specific installer like Anaconda for RedHat-based or DebianInstaller for debian-based. On the other hand Calamares is a graphical installer which is not aligned with only one package manager.
I want to share my experience how to make a universal install solution with GUI. I did not find any complete article about it, hence, I reinvented the wheel.
In this article I will show the following things:
- how-to build Calamares from the source
- how-to install Calamares in the system (be careful, it should be a live system)
- how-to install arch-based distro
- how to make branding of your installer
I will use stripped configs with only required options.
Building from the source
There are 2 parts of calamares:
- calamares (main program to install any linux) — mandatory
- calamares-extension (pack of files for branding) — optional
Calamares is a cmake-project based on QT. Therefore, general build and install are simple:
# create dir for this work
rm -rf calamares
mkdir -p calamares
cd calamares
# download sources from github
wget https://github.com/calamares/calamares/releases/download/v3.2.49.1/calamares-3.2.49.1.tar.gz
# untar it
tar xvpf calamares-3.2.49.1.tar.gz
cd calamares-3.2.49.1
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DWITH_PYTHONQT=ON -DAppStreamQt_DIR=... ..
Looks like instructions can be used to build calamares-extension, thus I skip them.
But I prefer to create pacman package to store an already built package and use it whenever I want. I wrote PKGBUILD file for calamares and calamares-extensions.
Calamares PKGBUILD for arch
# Maintainer: Alexey Soname "boozlachu"
pkgname=calamares
pkgver=3.2.49.1
pkgrel=1
pkgdesc='Universal linux distro graphical installer'
arch=('x86_64')
license=('GPL3')
url='https://calamares.io/'
depends=('binutils' 'fakeroot' 'gcc' 'boost' 'patch' 'qt5-tools' 'yaml-cpp' 'ack' 'kpmcore' 'qt5-location' 'icu' 'qt5-declarative' 'qt5-translations' 'qt5-xmlpatterns' 'kiconthemes' 'kservice' 'kio' 'kparts' 'cmake' 'autoconf' 'automake' 'bison' 'flex' 'git' 'libtool' 'm4' 'make' 'extra-cmake-modules' 'appstream-qt' 'squashfs-tools' 'fish' 'libpwquality' 'python3' 'python-qt.py' 'python-qtpy' 'qt5-webengine' 'python-pip')
source=("https://github.com/calamares/calamares/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz")
sha256sums=('86bdceb90eb81d42596b780ccd99385002e2b8d45d8fae836156d37b5122d020')
prepare() {
cd $pkgname-$pkgver
# apply patch from the source array (should be a pacman feature)
local filename
for filename in "${source[@]}"; do
if [[ "$filename" =~ \.patch$ ]]; then
echo "Applying patch ${filename##*/}"
patch -p1 -N -i "$srcdir/${filename##*/}"
fi
done
# :
PIP_CONFIG_FILE=/dev/null pip install --isolated --root="$pkgdir" --ignore-installed --no-deps pylint
}
build() {
cd $pkgname-$pkgver
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DWITH_PYTHONQT=ON -DAppStreamQt_DIR=... ..
}
package() {
cd $pkgname-$pkgver/build
make DESTDIR="$pkgdir/" install
}
Calamares-extensions PKGBUILD for arch
# Maintainer: Alexey Soname "boozlachu"
pkgname=calamares-extensions
pkgver=1.2.1
pkgrel=1
pkgdesc='Calamares Branding and Module Examples'
arch=('x86_64')
license=('GPL3')
url='https://calamares.io/'
depends=('binutils' 'fakeroot' 'gcc' 'boost' 'patch' 'qt5-tools' 'yaml-cpp' 'ack' 'kpmcore' 'qt5-location' 'icu' 'qt5-declarative' 'qt5-translations' 'qt5-xmlpatterns' 'kiconthemes' 'kservice' 'kio' 'kparts' 'cmake' 'autoconf' 'automake' 'bison' 'flex' 'git' 'libtool' 'm4' 'make' 'extra-cmake-modules' 'appstream-qt' 'squashfs-tools' 'fish' 'libpwquality' 'python3' 'python-qt.py' 'python-qtpy' 'qt5-webengine' 'python-pip')
source=("https://github.com/calamares/$pkgname/releases/download/v1.2.1/calamares-extensions-1.2.1.tar.gz")
sha256sums=('2e3514085cff8c816a0bffedcf0fa6ce2e7ab21e612557008129408b7cce3ad7')
prepare() {
cd $pkgname-$pkgver
# apply patch from the source array (should be a pacman feature)
local filename
for filename in "${source[@]}"; do
if [[ "$filename" =~ \.patch$ ]]; then
echo "Applying patch ${filename##*/}"
patch -p1 -N -i "$srcdir/${filename##*/}"
fi
done
PIP_CONFIG_FILE=/dev/null pip install --isolated --root="$pkgdir" --ignore-installed --no-deps pylint
}
build() {
cd $pkgname-$pkgver
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DWITH_PYTHONQT=ON -DAppStreamQt_DIR=... ..
}
package() {
cd $pkgname-$pkgver/build
make DESTDIR="$pkgdir/" install
}
And now you are able to build both packages via makepkg:
alexey@comp:~/test/calamares$ makepkg --syncdeps
You must run this command in the directory with PKGBUILD file and from a non-privileged user. Definitely it's a simplified example, you need to view more in the arch linux manual (see the links in the end).
Installing
There are 2 general ways to install calamares:
- using included scripts+
- using package
Via pacman package (Arch linux)
Ok, to install both packages (built in the previous step) you can use packman:
pacman -U ./calamares-3.2.49.1-1-x86_64.pkg.tar.zst --noconfirm
pacman -U ./calamares-extensions-1.2.1-1-x86_64.pkg.tar.zst --overwrite '*' --noconfirm
Argument "--overwrite '*'" is needed because both packages have+ default branding files.
Via included script
I have not used this way but you can read more here
Configuring
There are 3 general parts of Calamares configuring:
- settings.conf, major config
- module-specific configs
- branding (optional)
Ok, now we have built calamares, installed in our live system. Consequently, we must write configs for it.
First config for using is /etc/calamares/settings.conf. This file stores major settings and a list of used modules in a proper order.
There are dozens of available modules. Besides, everyone is free to write own modules. Every module performs some jobs. Good practice is to put configs for them into /etc/calamares/modules/:
alexey@comp:~$ tree /etc/calamares/modules/
/etc/calamares/modules/
├── bootloader.conf
├── displaymanager.conf
├── finished.conf
├── fstab.conf
├── initcpio.conf
├── initramfs.conf
├── locale.conf
├── machineid.conf
├── mhwdcfg.conf
├── mount.conf
├── packages.conf
├── partition.conf
├── postcfg.conf
├── services-systemd.conf
├── shellprocess.conf
├── unpackfs.conf
├── users.conf
└── welcome.conf
General settings
Now I will write a general config file /etc/calamares/settings.conf. First of all, I will add required modules. There are 2 parts of their sequence (and also 2 types of modules, some of them hybrid):
- show — these modules show something to user, they are used for interaction with a user
- exec — these modules do some jobs as soon as the install config is ready.
My /etc/calamares/settings.conf is:
modules-search: [ local ]
sequence:
- show:
- welcome
- locale
- keyboard
- partition
- users
- summary
- exec:
- partition
- mount
- unpackfs
- packages
- machineid
- fstab
- locale
- keyboard
- localecfg
- initcpiocfg
- initcpio
- users
- displaymanager
- networkcfg
- hwclock
- services-systemd
- bootloader
- umount
- show:
- finished
branding: boozelinux
prompt-install: false
dont-chroot: false
oem-setup: false
disable-cancel: false
disable-cancel-during-exec: false
hide-back-and-next-during-exec: false
quit-at-end: false
I will use basic config which will do simple things (SHOW part):
- show a welcome screen (module welcome)
- ask user about locale and timezone (module locale)
- ask user what keyboard layout will be used (module keyboard)
- set disk partitioning (module partition )
- set users (module users)
- show summary install settings (module summary)
And then calamares will install my linux (EXEC part), I will elaborate on this part later. Short descriptions are:
- partition the disk
- mount disk in future mode (like they will mount in future boot of your system), including /proc /sys /run /dev and others
- unpack files from squashfs to disk
- install packages inside future system
- create machine-id and other random data inside future system
- create fstab
- set locale
- set keyboard preferences
- create initcpio config and run mkinitcpio
- setup users
- configure display managers
- setup network
- set hardware clock
- setup systemd-services
- configure and install bootloader
- umount all in future filesystem
And last part is setting of some variables (more information in calamares git):
branding: boozelinux — use branding files from "boozelinux" directory
prompt-install: false — don’t ask user "Are you sure?" before each exec module
Don’t-chroot: false — don't use chroot to execute all target environment commands
oem-setup: false — don't use preconfigured install (I want to interact with a user)
disable-cancel: false — show DISABLE button
disable-cancel-during-exec: false — show DISABLE button after start of installation process
hide-back-and-next-during-exec: false — show BACK button after start of
installation process
quit-at-end: false — show finished-screen (with results of installation)
Complete instructions for ALL modules can be found here. To read the manual for each module please open *.conf file in even module dir. For example, open
'https://github.com/calamares/calamares/blob/calamares/src/modules/mount/mount.conf' to read module 'mount' config.
Branding (optional)
First of all, I will setup branding. You can skip this part but you will need to make some amendments in your settings.txt. Full guide is provided here: here: Calamares branding guide
Your directory with branding must be placed in /usr/share/calamares/branding, I will use /usr/share/calamares/branding/boozelinux. All next files will be placed inside this dir.
/usr/share/calamares/branding/boozelinux/
├── boozelinux1.png
├── boozelinux2.png
├── boozelinux3.png
├── boozelinux4.png
├── boozelinux5.png
├── Boozlachu.png
├── branding.desc
├── ImageSlide.qml
└── show.qml
0 directories, 9 files
branding.desc
This file stores settings of you calamares theme:
---
# show be equal to name of branding directory
componentName: boozelinux
# There are different style of welcome screen, use simple default
welcomeStyleCalamares: false
# I want to scale logo
welcomeExpandingLogo: true
# type if window, I dont want to use fullscreen
windowExpanding: normal
# Position of window
windowPlacement: center
# Type of panel left (which is showing progress)
sidebar: widget
# Placement of navigation panel
navigation: widget
# This string is used to write windows labels,headers and different texts.
strings:
productName: Boozelinux
shortProductName: Boozelinux
version: 2021.12
shortVersion: 2021.12
versionedName: Boozlechu GNU/Linux 2021.12 LTS "Smiling dragon"
shortVersionedName: BoozleLinux 2021.12
bootloaderEntryName: Boozelinux
productUrl: https://github.com/skif-web/
# link on button "support"
supportUrl: https://github.com/skif-web//wiki
# link on button "suppKnown issues"
knownIssuesUrl: https://github.com/skif-web//issues
# link on button "Release notes"
releaseNotesUrl: https://github.com/skif-web//news/
images:
# logo in windows header
productIcon: "Boozlachu.png"
# logo in sidebar
productLogo: "Boozlachu.png"
# Colors used in window
style:
sidebarBackground: "#292F34"
sidebarText: "#FFFFFF"
sidebarTextSelect: "#292F34"
sidebarTextHighlight: "#D35400"
# Slideshow is showed during install process. I use external file to make it
slideshow: "show.qml"
# Version of calamares API, I use modern version
slideshowAPI: 2
show.qml
import QtQuick 2.0 // Basic QML
import calamares.slideshow 1.0 // Calamares slideshow: Presentation
import io.calamares.ui 1.0 // Calamares internals: Branding
Presentation
{
id: presentation
Timer {
// Interval between pictures
interval: 3000
running: presentation.activatedInCalamares
repeat: true
onTriggered: presentation.goToNextSlide()
}
function onActivate() { }
function onLeave() { }
Rectangle {
id: mybackground
anchors.fill: parent
color: Branding.styleString(Branding.SidebarBackground)
z: -1
}
// list of slides
ImageSlide {
src: "boozelinux1.png"
}
ImageSlide {
src: "boozelinux2.png"
}
ImageSlide {
src: "boozelinux3.png"
}
ImageSlide {
src: "boozelinux4.png"
}
ImageSlide {
src: "boozelinux5.png"
}
}
I did it from the examples, compiling different files. The cherry-picking part is:
- interval: 3000 — change slides every 3 sec
- ImageSlide — every new slide is described in its own parts
ImageSlide.qml
This file defines the setting for pictures inside slideshow, called from previous file. I changed only 2 options: width and height:
import QtQuick 2.5
Item {
id: imageslide
visible: false
anchors.fill: parent
property bool isSlide: true;
property string notes;
property string src;
Image {
id: image
source: src
width: 600
height: 300
anchors.centerIn: parent
}
}
Images
All pictures are in png format:
- Boozlachu.png — a logo in slidebar and in a windows header
- boozelinux{1-5}.png — pictures for slideshow
Some screenshots
Modules’ descriptions and configs
Eventually we have 2 of 3 parts of configuring: mandatory config and branding. Now we must write modules-specific configs.
These custom configs must be placed inside /etc/calamares/modules/ directory.
output/target/etc/calamares/modules/
├── bootloader.conf
├── displaymanager.conf
├── finished.conf
├── fstab.conf
├── initcpio.conf
├── initramfs.conf
├── locale.conf
├── machineid.conf
├── mhwdcfg.conf
├── mount.conf
├── packages.conf
├── partition.conf
├── postcfg.conf
├── services-systemd.conf
├── shellprocess.conf
├── unpackfs.conf
├── users.conf
└── welcome.conf
0 directories, 18 files
I will describe modules config in call-order.
Show-modules
These modules are used to interact with a user.
Module welcome
This module shows welcome-screen, information will be read from the branding file.
---
# show or not buttons "support/issues/etc"
# links stored in branding.desc
showSupportUrl: true
showKnownIssuesUrl: true
showReleaseNotesUrl: true
# check hardware requirements
requirements:
requiredStorage: 7.9
requiredRam: 1.0
internetCheckUrl: https://manjaro.org
check:
- storage
- ram
- power
- internet
- root
# if broken, installer show error screen and prevent install
required:
- storage
- ram
- root
# Try to set language if internet available
geoip:
style: "json"
url: "https://ipapi.co/json"
selector: "country"
module locale
This module helps to select a timezone, a system language and locale settings like numbers/date formats.
---
localeGenPath: /etc/locale.gen
geoip:
style: "json"
url: "https://ipapi.co/json"
selector: "timezone"
module keyboard
This module setups a keyboard. I have not written config for it, default settings are ok.
module partition
This module allows to change disk partitioning. You are able to set some default settings for it.
---
efiSystemPartition: "/boot/efi"
userSwapChoices:
- none # Create no swap, use no swap
- small # Up to 4GB
- suspend # At least main memory size
- file # To swap file instead of partition
alwaysShowPartitionLabels: true
# There are four options: erase, replace, alongside, manual),
# the default is "none".
initialPartitioningChoice: erase
initialSwapChoice: none
defaultFileSystemType: "ext4"
availableFileSystemTypes: ["ext4","btrfs","f2fs","xfs"]
module users
This module is used to setup users in future system.
---
defaultGroups:
- lp
- network
- power
- sys
- wheel
autologinGroup: autologin
doAutologin: false
sudoersGroup: wheel
setRootPassword: true
doReusePassword: false
availableShells: /bin/bash, /bin/zsh
avatarFilePath: ~/.face
userShell: /bin/bash
module summary
This module shows settings of future install after all choices are made. I have not written custom config for it, default is ok.
module finished
This module is used to show install results.
---
# show "reboot" check
restartNowEnabled: true
# dont's check it by default
restartNowChecked: false
# command for reboot
restartNowCommand: "systemctl reboot"
Exec-modules
These modules never interact with a user. They are workers that install your system. You will see slideshow during their work.
module partition
This module provides partitioning: creates partition table and partitions, formats them, etc.
module mount
# Mount needed inside future filesystem, it's preparation to chroot.
# Filesystem listed in future fstab will be mounted automatically.
extraMounts:
- device: proc
fs: proc
mountPoint: /proc
- device: sys
fs: sysfs
mountPoint: /sys
- device: /dev
mountPoint: /dev
options: bind
- device: tmpfs
fs: tmpfs
mountPoint: /run
- device: /run/udev
mountPoint: /run/udev
options: bind
# only if it's UEFI system
extraMountsEfi:
- device: efivarfs
fs: efivarfs
mountPoint: /sys/firmware/efi/efivars
module unpackfs
This module unpacks initial filesystem from squashfs. You can use multiple files.
# select package manager
backend: pacman
# allow to install without Internet
skip_if_no_internet: false
# update package database like "pacman -Sy" inside target system
update_db: true
# update packages inside target system
update_system: true
pacman:
# number of retries if failure happen.
num_retries: 0
# --disable-download-timeout for pacman
disable_download_timeout: false
# pacman --needed argument
needed_only: true
operations:
# packages for install
- install:
- xfce4
- vim
- grub
- wget
- nano
- bash
module machineid
This module setups any required random data.
---
# /etc/machine-i
systemd: true
# /var/lib/dbus/machine-id
dbus: true
# Whether /var/lib/dbus/machine-id should be a symlink to /etc/machine-id
dbus-symlink: true
module fstab
This module creates fstab in the target system.
---
# default mountpoints settings
mountOptions:
default: defaults,noatime
btrfs: defaults
btrfs_swap: defaults
# for uefi part
efiMountOptions: umask=0077
# if SSD detected
ssdExtraMountOptions:
btrfs: discard=async,ssd
# for crypttab (if LUKS used)
crypttabOptions: luks
module localecfg
This module configures locales. Default config is ok.
module initcpiocfg
This module creates mkinitcpio.conf inside the target system.
module initcpio
This module runs mkinitcpio command.
---
# name of kernel preset
kernel: linux
module displaymanager
This module setups display managers. I prefer to use lightdm.
---
displaymanagers:
- lightdm
basicSetup: true
module networkcfg
This module setups network configuration
module hwclock
This module sets hardware clock.
module services-systemd
This module can enable/disable services/timers and targets. Besides you are able to set default target.
services:
- name: avahi-daemon
mandatory: false
- name: bluetooth
mandatory: false
- name: cronie
mandatory: false
- name: ModemManager
mandatory: false
- name: NetworkManager
mandatory: true
- name: cups
mandatory: true
- name: tlp
mandatory: false
- name: haveged
mandatory: false
- name: ufw
mandatory: false
- name: apparmor
mandatory: false
- name: snapd.apparmor
mandatory: false
- name: snapd
mandatory: false
- name: fstrim.timer
mandatory: false
- name: pkgfile-update.timer
mandatory: false
- name: lightdm
mandatory: true
targets:
- name: "graphical"
mandatory: true
disable:
- name: pacman-init
mandatory: false
module bootloader
This module setups bootloader. Different loaders are in support. I like to use grub.
---
efiBootLoader: "grub"
kernel: "/vmlinuz-5.13-x86_64"
img: "/initramfs-5.13-x86_64.img"
fallback: "/initramfs-5.13-x86_64-fallback.img"
timeout: "10"
kernelLine: ", with linux513"
fallbackKernelLine: ", with linux513 (fallback initramfs)"
grubInstall: "grub-install"
grubMkconfig: "grub-mkconfig"
grubCfg: "/boot/grub/grub.cfg"
grubProbe: "grub-probe"
efiBootMgr: "efibootmgr"
installEFIFallback: true
module umount
This module umounts everything inside target filesystem.
shellprocess (not used but interesting)
I never use this module, but probably someone will. It provides shell commands run inside the target system.
Conclusions
Currently I am sure that Calamares may be used to install different linux distros including your own made. Some things look crazy but everything comes to be unclouded and consistent if you are eager to read documentation and out-if-box config.
Links
1) Calamares main page
1) Arch PKGBUILD
1) Arch creating packages
1) Calamares quick deployment script
1) Calamares settings.txt example and guide
1) Full describes of calamares modules
1) Calamares branding guide
1) Calamares module descriptions