~/2014/10/14

Installing Arch Linux on the Lenovo x240

I got a new computer recently. Since I moved closer to a train line I've been spending heaps of time on public transport and my daily commute is a four-hour round trip. My hand-me-down road warrior couldn't take it, and would flatline at around 4 hours even on the most conservative power settings.

So I wanted something with a better battery life. The major contenders were the Mac Air, Samsung ATIV Book 9 and the Lenovo x240. The Mac was ruled out because of its weird keyboard layout (using Emacs without symmetrical modifiers is a special kind of hell), and the Samsung because it was difficult to get in Australia (and apparently they've recently abandoned the laptop market).

The x240 has some issues of its own, but I'm not interested in writing a review. I'm writing this because it was my first experience with UEFI, and this is stuff I'm likely to forget next time it comes around. Here's what I did after I received it.

Update the BIOS

I heard there were a few issues ironed out by a recent BIOS update, so I wanted to make sure to have the most recent one. This was a bit harder than it should have been. Lenovo provides BIOS update images, but only in ISO 9660 CD-ROM format. You can't make a bootable USB drive without extracting the El Torito image first. There's a Perl script you can use, or you can read the spec and figure out how many bytes you should tell dd to skip when extracting it.

Bye Windows

In order to grab the output I'll be installing over SSH from my desktop.

After booting into the Arch ISO, you just need to start sshd and set a root password. I'm using a wired connection, so the dhcp lease has already been negotiated.

~ $ ssh root@192.168.1.6
The authenticity of host '192.168.1.6 (192.168.1.6)' can't be established.
ECDSA key fingerprint is a9:7f:61:65:73:81:52:ca:be:6c:08:36:c9:c8:2c:e5.
Are you sure you want to continue connecting (yes/no)? yes

Warning: Permanently added '192.168.1.6' (ECDSA) to the list of known hosts.
root@192.168.1.6's password:
Last login: Mon Aug 18 03:12:04 2014
root@archiso ~ # lspci
00:00.0 Host bridge: Intel Corporation Haswell-ULT DRAM Controller (rev 0b)
00:02.0 VGA compatible controller: Intel Corporation Haswell-ULT Integrated
Graphics Controller (rev 0b)
00:03.0 Audio device: Intel Corporation Haswell-ULT HD Audio Controller (rev 0b)
00:14.0 USB controller: Intel Corporation 8 Series USB xHCI HC (rev 04)
00:16.0 Communication controller: Intel Corporation 8 Series HECI #0 (rev 04)
00:16.3 Serial controller: Intel Corporation 8 Series HECI KT (rev 04)
00:19.0 Ethernet controller: Intel Corporation Ethernet Connection I218-LM (rev
04)
00:1b.0 Audio device: Intel Corporation 8 Series HD Audio Controller (rev 04)
00:1c.0 PCI bridge: Intel Corporation 8 Series PCI Express Root Port 6 (rev e4)
00:1c.1 PCI bridge: Intel Corporation 8 Series PCI Express Root Port 3 (rev e4)
00:1d.0 USB controller: Intel Corporation 8 Series USB EHCI #1 (rev 04)
00:1f.0 ISA bridge: Intel Corporation 8 Series LPC Controller (rev 04)
00:1f.2 SATA controller: Intel Corporation 8 Series SATA Controller 1
[AHCI mode] (rev 04)
00:1f.3 SMBus: Intel Corporation 8 Series SMBus Controller (rev 04)
02:00.0 Unassigned class [ff00]: Realtek Semiconductor Co., Ltd. RTS5227 PCI
Express Card Reader (rev 01)
03:00.0 Network controller: Intel Corporation Wireless 7260 (rev 83)
root@archiso ~ # efivar -l | tail
955b9041-133a-4bcf-90d1-97e1693c0e30-SecureBootOption
0b7646a4-6b44-4332-8588-c8998117f2ef-ProtectedBootOptions
2a4dc6b7-41f5-45dd-b46f-2dd334c1cf65-LBL
8be4df61-93ca-11d2-aa0d-00e098032b8c-SignatureSupport
8be4df61-93ca-11d2-aa0d-00e098032b8c-SecureBoot
8be4df61-93ca-11d2-aa0d-00e098032b8c-SetupMode
8b604cac-3c4f-4e6c-862e-00b8b7436e5f-PreBootEventLogReset
23771b23-e15a-4805-920a-4f1e84b54abc-AoacWakeStatus
6403753b-abde-4da2-aa11-6983ef2a7a69-TpmAcpiData
5e724c0c-5c03-4543-bcb6-c1e23de24136-TpmSaveState
root@archiso ~ # lsblk
NAME            MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda               8:0    0 238.5G  0 disk
├─sda1            8:1    0   512M  0 part
└─sda2            8:2    0   238G  0 part
sdb               8:16   1     2G  0 disk
├─sdb1            8:17   1   559M  0 part /run/archiso/bootmnt
└─sdb2            8:18   1    31M  0 part
loop0             7:0    0 241.3M  1 loop /run/archiso/sfs/airootfs
loop1             7:1    0    32G  1 loop
└─arch_airootfs 254:0    0    32G  0 dm   /
loop2             7:2    0    32G  0 loop
└─arch_airootfs 254:0    0    32G  0 dm   /

There are some spooky restrictions on the size of UEFI partitions that I don't understand. You can't see it here because I'd already cocked up once and started over by this point, but the EFI partition used by Windows was 260MB so I'll follow suit.

root@archiso ~ # gdisk /dev/sda
GPT fdisk (gdisk) version 0.8.10

Partition table scan:
  MBR: protective
  BSD: not present
  APM: not present
  GPT: present

Found valid GPT with protective MBR; using GPT.

Command (? for help): o
o
This option deletes all partitions and creates a new protective MBR.
Proceed? (Y/N): y
y

Command (? for help): n
n
Partition number (1-128, default 1):

First sector (34-500118158, default = 2048) or {+-}size{KMGTP}:

Last sector (2048-500118158, default = 500118158) or {+-}size{KMGTP}: +260M
+260M
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): EF00
EF00
Changed type of partition to 'EFI System'

Command (? for help): n
n
Partition number (2-128, default 2):

First sector (34-500118158, default = 534528) or {+-}size{KMGTP}:

Last sector (534528-500118158, default = 500118158) or {+-}size{KMGTP}:

Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): 8e00
8e00
Changed type of partition to 'Linux LVM'

Command (? for help): p
p
Disk /dev/sda: 500118192 sectors, 238.5 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): E0235DAB-F1F1-4A85-9EF8-371F60D1AF8B
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 500118158
Partitions will be aligned on 2048-sector boundaries
Total free space is 2014 sectors (1007.0 KiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048          534527   260.0 MiB   EF00  EFI System
   2          534528       500118158   238.2 GiB   8E00  Linux LVM

Command (? for help): w
w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): y
y
OK; writing new GUID partition table (GPT) to /dev/sda.
The operation has completed successfully.

Create filesystems

I've been using the same partition layout since I started using GNU/Linux full-time: one boot partition plus encrypted root, swap and home. Since I am the only user of this system it makes sense to group the last three in an LVM container so that they can all be unlocked at once.

root@archiso ~ # cryptsetup -v --cipher aes-xts-plain64 --key-size 512 --hash sha512 luksFormat /dev/sda2

WARNING!
========
This will overwrite data on /dev/sda2 irrevocably.

Are you sure? (Type uppercase yes): YES
YES
Enter passphrase:
Verify passphrase:
Command successful.

root@archiso ~ # cryptsetup luksOpen /dev/sda2 lvm
Enter passphrase for /dev/sda2:
root@archiso ~ # lvm pvcreate /dev/mapper/lvm
  Physical volume "/dev/mapper/lvm" successfully created
root@archiso ~ # lvm vgcreate vgroup /dev/mapper/lvm
  Volume group "vgroup" successfully created
root@archiso ~ # lvm lvcreate -L 20G -n root vgroup
  Logical volume "root" created
root@archiso ~ # lvm lvcreate -L 16G -n swap vgroup
  Logical volume "swap" created
root@archiso ~ # lvm lvcreate -l 100%FREE -n home vgroup
  Logical volume "home" created

root@archiso ~ # mkfs.fat -F32 /dev/sda1
mkfs.fat 3.0.26 (2014-03-07)
root@archiso ~ # mkfs.ext4 /dev/mapper/vgroup-root
mke2fs 1.42.10 (18-May-2014)
Creating filesystem with 5242880 4k blocks and 1310720 inodes
Filesystem UUID: a92f12a2-f546-4fb1-a11a-230685be8328
Superblock backups stored on blocks:
    32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, 4096000

Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

root@archiso ~ # mkfs.ext4 /dev/mapper/vgroup-home
mke2fs 1.42.10 (18-May-2014)
Creating filesystem with 53009408 4k blocks and 13254656 inodes
Filesystem UUID: fed21fe7-66c0-4f39-95b1-d7bb046538b5
Superblock backups stored on blocks:
    32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, 4096000, 7962624, 11239424, 20480000, 23887872

Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

root@archiso ~ # mkswap /dev/mapper/vgroup-swap
Setting up swapspace version 1, size = 16777212 KiB
no label, UUID=5819cdd5-b8c2-45bb-a4a9-e9a93362b31b

root@archiso ~ # mount /dev/mapper/vgroup-root /mnt
root@archiso ~ # mkdir /mnt/{boot,home}
root@archiso ~ # ls /mnt
boot/  home/  lost+found/
root@archiso ~ # mount /dev/mapper/vgroup-home /mnt/home
root@archiso ~ # mount /dev/sda1 /mnt/boot
root@archiso ~ # swapon /dev/mapper/vgroup-swap

Installing the base system

Edit the mirrorlist to prepare for pacstrap. I'm an Internode customer and they have unmetered mirrors, so I preference theirs. Be sure to quote the URL so that the shell doesn't eat the ampersand.

root@archiso ~ # curl -o /etc/pacman.d/mirrorlist "https://www.archlinux.org/mirrorlist/?country=AU&protocol=http&use_mirror_status=ON"

Now we just need to uncomment the server lines.

root@archiso ~ # sed -i 's/^#S/S/g' /etc/pacman.d/mirrorlist
root@archiso ~ # cat !$
##
## Arch Linux repository mirrorlist
## Sorted by mirror score from mirror status page
## Generated on 2014-08-17
##

## Score: 1.5, Australia
Server = http://mirror.internode.on.net/pub/archlinux/$repo/os/$arch
## Score: 1.8, Australia
Server = http://mirror.aarnet.edu.au/pub/archlinux/$repo/os/$arch
## Score: 1.8, Australia
Server = http://ftp.swin.edu.au/archlinux/$repo/os/$arch
## Score: 3.8, Australia
Server = http://mirror.rackcentral.com.au/archlinux/$repo/os/$arch
## Score: 4.3, Australia
Server = http://archlinux.mirror.uber.com.au/$repo/os/$arch
## Score: 5.0, Australia
Server = http://mirror.optus.net/archlinux/$repo/os/$arch
## Score: 13.4, Australia
Server = http://syd.mirror.rackspace.com/archlinux/$repo/os/$arch
## Score: 19.1, Australia
Server = http://ftp.iinet.net.au/pub/archlinux/$repo/os/$arch

Grab every package from the base and base-devel groups.

root@archiso ~ # pacstrap /mnt base base-devel

Time to generate the fstab, be sure to add discard to the options for your new partitions if your model has an SSD. I'll use the same options I have always used for the two ext4 partitions:

root@archiso ~ # genfstab -U -p /mnt >> /mnt/etc/fstab
root@archiso ~ # sed -i 's/rw,relatime,data=ordered/defaults,noatime,discard/g' /etc/fstab

Now we can chroot into the new system.

root@archiso ~ # arch-chroot /mnt /bin/bash
[root@archiso /]# sed -i 's/^#en_US.UTF-8/en_US.UTF-8/' /etc/locale.gen
[root@archiso /]# locale-gen
Generating locales...
  en_US.UTF-8... done
Generation complete.
[root@archiso /]# echo LANG=en_US.UTF-8 > /etc/locale.conf
[root@archiso /]# export LANG=en_US.UTF-8
[root@archiso /]# ln -s /usr/share/zoneinfo/Australia/Adelaide /etc/localtime
[root@archiso /]# hwclock --systohc --utc
[root@archiso /]# echo ayanami > /etc/hostname
[root@archiso /]# sed -i 's/localhost$/localhost\tayanami/' /etc/hosts

Be sure to add encrypt, lvm2 and resume to the hooks line in mkinitcpio.conf, and i915 to modules so that we get the full resolution early in the boot:

[root@archiso /]# sed -i '/^HOOKS/s/block/block encrypt lvm2 resume/'
/etc/mkinitcpio.conf
[root@archiso /]# sed -i '/^MODULES/s/""/"i915"/' !$

Then rebuild the initramfs so that our hooks get included:

[root@archiso /]# mkinitcpio -p linux
==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'default'
  -> -k /boot/vmlinuz-linux -c /etc/mkinitcpio.conf -g /boot/initramfs-linux.img
==> Starting build: 3.16.1-1-ARCH
  -> Running build hook: [base]
  -> Running build hook: [udev]
  -> Running build hook: [autodetect]
  -> Running build hook: [modconf]
  -> Running build hook: [block]
  -> Running build hook: [encrypt]
  -> Running build hook: [lvm2]
  -> Running build hook: [resume]
  -> Running build hook: [filesystems]
  -> Running build hook: [keyboard]
  -> Running build hook: [fsck]
==> Generating module dependencies
==> Creating gzip-compressed initcpio image: /boot/initramfs-linux.img
==> Image generation successful
==> Building image from preset: /etc/mkinitcpio.d/linux.preset: 'fallback'
  -> -k /boot/vmlinuz-linux -c /etc/mkinitcpio.conf -g
/boot/initramfs-linux-fallback.img -S autodetect
==> Starting build: 3.16.1-1-ARCH
  -> Running build hook: [base]
  -> Running build hook: [udev]
  -> Running build hook: [modconf]
  -> Running build hook: [block]
==> WARNING: Possibly missing firmware for module: aic94xx
==> WARNING: Possibly missing firmware for module: smsmdtv
  -> Running build hook: [encrypt]
  -> Running build hook: [lvm2]
  -> Running build hook: [resume]
  -> Running build hook: [filesystems]
  -> Running build hook: [keyboard]
  -> Running build hook: [fsck]
==> Generating module dependencies
==> Creating gzip-compressed initcpio image: /boot/initramfs-linux-fallback.img
==> Image generation successful

And set a root password:

[root@archiso /]# passwd

Also tell lvm to issue_discards in case we ever need to remove a volume or change its size:

[root@archiso /]# sed -i "s/issue_discards = 0/issue_discards = 1/" /etc/lvm/lvm.conf

Install the bootloader

Finally it's time to install the bootloader. I'm a fan of syslinux and recent versions have UEFI support, so we'll be using that. First we need to install a few packages:

[root@archiso /]# pacman -S gptfdisk syslinux efibootmgr dosfstools

The bundled install script doesn't work for EFI systems yet, but it's still pretty easy:

[root@archiso /]# mkdir /boot/EFI
[root@archiso /]# mv /boot/syslinux !$
[root@archiso /]# cp /usr/lib/syslinux/efi64/* /boot/EFI/syslinux

Currently, the partition layout looks like this:

[root@archiso /]# lsblk
NAME              MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
sda                 8:0    0 238.5G  0 disk
├─sda1              8:1    0   260M  0 part  /boot
└─sda2              8:2    0 238.2G  0 part
  └─lvm           254:1    0 238.2G  0 crypt
    ├─vgroup-root 254:2    0    20G  0 lvm   /
    ├─vgroup-swap 254:3    0    16G  0 lvm   [SWAP]
    └─vgroup-home 254:4    0 202.2G  0 lvm   /home
sdb                 8:16   1     2G  0 disk
├─sdb1              8:17   1   559M  0 part
└─sdb2              8:18   1    31M  0 part
loop0               7:0    0 241.3M  1 loop
loop1               7:1    0    32G  1 loop
└─arch_airootfs   254:0    0    32G  0 dm    /etc/resolv.conf
loop2               7:2    0    32G  0 loop
└─arch_airootfs   254:0    0    32G  0 dm    /etc/resolv.conf
[root@archiso /]# blkid
/dev/sda1: UUID="53EA-7453" TYPE="vfat" PARTLABEL="EFI System"
PARTUUID="96f51a73-e502-4980-8ca1-8ca5db2ed609"
/dev/sda2: UUID="bcd85b6e-f825-46da-82aa-2ae4918f5daa" TYPE="crypto_LUKS"
PARTLABEL="Linux LVM" PARTUUID="911c24cc-5291-46b6-a734-2e8e18d8c89a"
/dev/sdb1: UUID="2014-08-01-03-12-56-00" LABEL="ARCH_201408" TYPE="iso9660"
PTUUID="4433a768" PTTYPE="dos" PARTUUID="4433a768-01"
/dev/sdb2: SEC_TYPE="msdos" LABEL="ARCHISO_EFI" UUID="5742-45C2" TYPE="vfat"
PARTUUID="4433a768-02"
/dev/loop0: TYPE="squashfs"
/dev/loop1: UUID="548ba75e-8e6f-4cda-931b-a193d8a448cc" TYPE="ext4"
/dev/loop2: UUID="548ba75e-8e6f-4cda-931b-a193d8a448cc" TYPE="ext4"
/dev/mapper/arch_airootfs: UUID="548ba75e-8e6f-4cda-931b-a193d8a448cc"
TYPE="ext4"
/dev/mapper/lvm: UUID="apLcOM-NENQ-oA3b-KAPk-zRdY-tAiG-Nr2GbM"
TYPE="LVM2_member"
/dev/mapper/vgroup-root: UUID="a92f12a2-f546-4fb1-a11a-230685be8328" TYPE="ext4"
/dev/mapper/vgroup-swap: UUID="5819cdd5-b8c2-45bb-a4a9-e9a93362b31b" TYPE="swap"
/dev/mapper/vgroup-home: UUID="fed21fe7-66c0-4f39-95b1-d7bb046538b5" TYPE="ext4"

The "EFI System" partition lives in /dev/sda1, so use /dev/sda for the next step:

[root@archiso /]# efibootmgr -c -d /dev/sda -p 1 -l /EFI/syslinux/syslinux.efi
-L "Syslinux"
BootCurrent: 000C
Timeout: 0 seconds
BootOrder: 0013,0007,0008,0009,000A,000B,000C,000D
Boot0000  Setup
Boot0001  Boot Menu
Boot0002  Diagnostic Splash Screen
Boot0003  Lenovo Diagnostics
Boot0004  Startup Interrupt Menu
Boot0005  Rescue and Recovery
Boot0006  MEBx Hot Key
Boot0007* USB CD
Boot0008* USB FDD
Boot0009* ATA HDD0
Boot000A* ATA HDD1
Boot000B* ATA HDD2
Boot000C* USB HDD
Boot000D* PCI LAN
Boot000E* IDER BOOT CDROM
Boot000F* IDER BOOT Floppy
Boot0010* ATA HDD
Boot0011* ATAPI CD
Boot0012* PCI LAN
Boot0013* Syslinux

Let's break that command down.

  • c means that we are creating a new boot entry,

  • d is the disk containing the loader (/dev/sda),

  • p is the partition number containing the bootloader (/dev/sda1),

  • l tells the UEFI where to look for the bootloader, and

  • L is just a label for the entry.

This is where I had a little trouble. Even though we've mounted our EFI partition on /boot, the UEFI is not aware of it because that directory exists on vgroup-root. So even though syslinux.efi might live at /boot/EFI/syslinux/syslinux.efi, use /EFI/syslinux/syslinux.efi here.

Now all that is left is to configure the bootloader. It's a bit too hairy to modify using sed so I'll just copy the relevant sections here. I like to use UUIDs for identifiers. At the time of writing syslinux doesn't have a means of breaking a long APPEND line up into multiple lines, but the wiki says that one will be added eventually.

# /boot/EFI/syslinux/syslinux.cfg

DEFAULT arch
PROMPT 1
TIMEOUT 5

LABEL arch
  MENU LABEL Arch Linux
  LINUX ../../vmlinuz-linux
  APPEND root=UUID=a92f12a2-f546-4fb1-a11a-230685be8328 cryptdevice=UUID=bcd85b6e-f825-46da-82aa-2ae4918f5daa:lvm:allow-discards resume=UUID=5819cdd5-b8c2-45bb-a4a9-e9a93362b31b rw
  INITRD ../../initramfs-linux.img

LABEL archfallback
  MENU LABEL Arch Linux Fallback
  LINUX ../../vmlinuz-linux
  APPEND root=UUID=a92f12a2-f546-4fb1-a11a-230685be8328 cryptdevice=UUID=bcd85b6e-f825-46da-82aa-2ae4918f5daa:lvm:allow-discards resume=UUID=5819cdd5-b8c2-45bb-a4a9-e9a93362b31b rw
  INITRD ../../initramfs-linux-fallback.img

Finishing up

All that's left to do is add a user. Of course you have to install zsh first if you want to use it.

[root@archiso /]# useradd -m -g users -G wheel,systemd-journal -s /bin/zsh
eqyiel
[root@archiso /]# passwd eqyiel
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
[root@archiso /]# exit
arch-chroot /mnt /bin/bash  7.71s user 1.39s system 0% cpu 1:15:07.28 total
root has logged on pts/1 from 192.168.1.50.
root@archiso ~ # systemctl reboot
Connection to 192.168.1.6 closed by remote host.
Connection to 192.168.1.6 closed.
~ $

At this point the laptop should be asking for a passphrase to unlock the encrypted volume. You'll probably also want to install some packages specific to the hardware: xf86-input-synaptics and xf86-video-intel.

The whole family.