Skip to content

Arch Lispy Setup: Installing Arch Linux declaratively with Guile Scheme

How to install Arch Linux without typing the same commands over and over again is a question this blog keeps coming back to. My latest approach was a set of Ansible playbooks. Since then, I went one step further and wrote a dedicated installer: Alise (short for Arch Lispy Setup). Like my Ansible playbooks, it installs Arch Linux remotely via SSH. But instead of YAML files, an installation is described by a short Guile Scheme script.

From Ansible to Alise

The Ansible playbooks work fine, but Ansible is a general-purpose automation tool, and installing Arch Linux is a rather special use case. My playbooks consist of roles that mimic the different steps of the Arch Linux installation guide, and the configuration of a system is spread across several YAML files. What I actually wanted was a tool that is built for exactly this one job: I describe what the system should look like, and the tool knows how to get there.

That's what Alise is about. It is

  • declarative: a script describes the target system, not the installation steps,
  • simple: a complete installation needs about ten lines of script,
  • sequential: there are no phases, the steps are executed one after the other in a fixed order,
  • safe: secrets are encrypted, and the target is checked thoroughly before anything destructive happens.

Why Scheme? A Scheme script is plain text (and can thus be put under version control), but - in contrast to YAML - it is also a real programming language. So, configuration can be shared across machines via variables, functions, or separate files without any additional templating mechanism. Admittedly, Lisp syntax is not everybody's cup of tea. Alise is not meant to be a mass-market installer, but a tool for people who like to describe their infrastructure as code.

How it works

As with Ansible, two computers are required:

  1. the one that is to be installed (the target), booted from the Arch Linux ISO,
  2. and a local machine where Alise runs and from which it connects to the target via SSH.

On the local machine, only Guile (version 3.0 or higher), guile-ssh, and GnuPG are needed. Alise does not even have to be installed - it can be run directly from a clone of the repository. Alternatively, it is available in the AUR and in the nerdstuff package repository.

A first installation

This is a complete installation script:

(use-modules (installer prelude))

(system!
  #:host "192.168.1.100"
  #:device "/dev/sda"
  #:hostname "mypc"
  #:timezone "Europe/Berlin"
  #:locale "de_DE.UTF-8"
  #:users '(("alice" #:groups ("wheel"))))

With it, Alise detects the CPU and installs the matching microcode package, partitions and formats the disk, installs the base system, sets the time zone, locale, and host name, installs NetworkManager, creates the user, and installs systemd-boot. The installation itself is started with

$ alise install install.scm

More complex setups

Of course, simple setups are not the interesting ones. Alise supports LUKS encryption, LVM on LUKS, and Btrfs with subvolumes, swap files sized according to the RAM, hibernation, and TRIM. Besides some built-in disk layouts, custom layouts can be defined. Thus, the setup I described here can be installed with Alise as well.

In addition, Alise can set up Secure Boot with a signed unified kernel image (using keys of the system's own) and unlock the LUKS container with the TPM and a PIN. For a laptop, this looks like:

(use-modules (installer prelude))

(system!
  #:host "192.168.1.100"
  #:device "/dev/nvme0n1"
  #:layout 'encrypted
  #:boot 'signed-uki
  #:luks-unlock 'tpm2-pin

  #:hostname "laptop"
  #:timezone "Europe/Berlin"
  #:locale "de_DE.UTF-8"
  #:keymap "de-latin1"

  #:users '(("alice" #:groups ("wheel"))))

The LUKS passphrase is kept as a recovery option, and if the TPM has to be bound again after a firmware update, alise-tpm-reenroll on the installed system does that.

There are many more configuration options: network connections (NetworkManager or systemd-networkd, Ethernet and WiFi, DHCP or static addresses), additional packages and pacman repositories, systemd services, configuration files that are copied onto the installed system, scripts that are executed there, etc.

Profiles and roles

To reuse configuration across machines, Alise provides two concepts:

  • A profile answers the question "what kind of machine is this?" (e.g., laptop or server). Profiles can inherit from each other: a "dev-laptop" profile can extend a "laptop" profile, which in turn extends a basic "machine" profile.
  • A role answers the question "what does this system do?" (e.g., web server or database). A role bundles repositories, packages, and services, and a system can play any number of roles.
(define web (role "web" #:packages '("nginx") #:services '("nginx")))
(define db  (role "db"  #:packages '("postgresql") #:services '("postgresql")))

Managing secrets

Passwords, passphrases, and SSH keys don't belong in an installation script. Similar to Ansible vault, Alise keeps them in an encrypted vault that is managed with alise-vault:

$ alise-vault create
$ alise-vault edit

Many secrets are used automatically, without having to be referenced in the script: luks-password for the LUKS passphrase, root-password for root, and <username>-password for each user. Missing secrets are asked for interactively - and all of them before Alise touches the target. Otherwise, it could happen that the LUKS passphrase is only asked for after the disk has already been wiped.

Before anything is touched

Wiping a disk is not something you want to do accidentally. That's why a large part of Alise is about knowing what will happen before it happens:

  • alise check verifies the target: Is it running the Arch ISO? Does the disk exist, and is it unused? Is the system booted in EFI mode? Can the package repositories be reached? alise install runs these checks automatically, and in addition, the configuration is checked against facts gathered from the target.
  • alise simulate shows every command that would be executed - without needing a target at all.
  • alise plan writes the complete installation as a Markdown document. It can be read to check an installation, kept as documentation, or even followed by hand.
  • alise try installs the script into a QEMU/KVM virtual machine - Secure Boot and TPM included - and boots the installed system in a window. That way, problems that only show at runtime (a failing script, a broken configuration file, a system that doesn't boot) can be found before a real machine is touched.

A typical sequence thus is:

$ alise simulate -v install.scm
$ alise try install.scm
$ alise install -l install.log install.scm

The log file contains a full, secret-safe record of the run, which is helpful if something goes wrong.

Nothing to do at the target's console

With the plain Arch ISO, some manual work at the target is still required before Alise can connect: set a password for root, determine the IP address, and check the fingerprint of the SSH host key. alise prepare creates an installation medium that makes this unnecessary: root logs in with my SSH key, the host key is known in advance, and the target can be reached by name (NAME.local) or via a fixed address. That's particularly useful for machines without screen and keyboard - a server that already runs Linux can even be reinstalled entirely over the network.

Limitations

Alise focuses on modern systems, and some limitations are intentional: only EFI (no BIOS), only GPT (no MBR), only systemd-boot (no GRUB), and no dual-boot. A few other things are not supported yet, such as static IPv6 addresses or resuming an interrupted installation.

If you want to give Alise a try, the getting started guide is a good starting point. Feedback is very welcome.

Comments