~/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.