Pull to refresh

Introducing into calamares bootloader

Reading time13 min
Views4.7K


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.



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

Tags:
Hubs:
+2
Comments0

Articles