Skip to content

Instantly share code, notes, and snippets.

@mtelvers
Created April 2, 2025 17:38
Show Gist options
  • Select an option

  • Save mtelvers/2cbeb5e35f43f5e461aa0c14c4a0a6b8 to your computer and use it in GitHub Desktop.

Select an option

Save mtelvers/2cbeb5e35f43f5e461aa0c14c4a0a6b8 to your computer and use it in GitHub Desktop.
Ansible Playbook to install Ubuntu 24.04 on a new machine with a ZFS root
- hosts: all
vars:
password: "password"
part1: []
part2: ""
part3: ""
part4: ""
tasks:
- name: Install apt packages
apt:
name:
- debootstrap
- gdisk
- zfsutils-linux
state: present
- name: Stop services
ansible.builtin.service:
name: "{{ item }}"
state: stopped
with_items:
- zed
- name: List disks
shell: "ls /dev/disk/by-id/wwn* | grep -v part"
register: ls
- set_fact:
disks: "{{ ls.stdout_lines }}"
- name: Create a list of names of all disks postfixed with '-part1'
set_fact:
part1: "{{ part1 + [ item + '-part1' ] }}"
with_items: "{{ disks }}"
- name: Create a variable of names of all disks postfixed with '-part2'
set_fact:
part2: "{{ part2 + item + '-part2 ' }}"
with_items: "{{ disks }}"
- name: Create a variable of names of all disks postfixed with '-part3'
set_fact:
part3: "{{ part3 + item + '-part3 ' }}"
with_items: "{{ disks }}"
- name: Create a variable of names of all disks postfixed with '-part4'
set_fact:
part4: "{{ part4 + item + '-part4 ' }}"
with_items: "{{ disks }}"
- name: Create bootloader partitions
shell: sgdisk -n1:1M:+512M -t1:EF00 {{ item }}
with_items: "{{ disks }}"
- name: Create swap partitions
shell: sgdisk -n2:0:+512M -t2:FD00 {{ item }}
with_items: "{{ disks }}"
- name: Create boot pool partitions
shell: sgdisk -n3:0:+512M -t3:BE00 {{ item }}
with_items: "{{ disks }}"
- name: Create root pool partitions
shell: sgdisk -n4:0:0 -t4:BF00 {{ item }}
with_items: "{{ disks }}"
- name: Create zfs pool bpool
shell: |
zpool create \
-o ashift=12 \
-o autotrim=on \
-o cachefile=/etc/zfs/zpool.cache \
-o compatibility=grub2 \
-o feature@livelist=enabled \
-o feature@zpool_checkpoint=enabled \
-O devices=off \
-O acltype=posixacl -O xattr=sa \
-O compression=lz4 \
-O normalization=formD \
-O relatime=on \
-O canmount=off -O mountpoint=/boot -R /mnt \
bpool raidz {{ part3 }}
- name: Create zfs pool rpool
shell: |
zpool create \
-o ashift=12 \
-o autotrim=on \
-O acltype=posixacl -O xattr=sa -O dnodesize=auto \
-O compression=lz4 \
-O normalization=formD \
-O relatime=on \
-O canmount=off -O mountpoint=/ -R /mnt \
rpool raidz {{ part4 }}
- name: Create filesystem datasets to act as containers
shell: |
zfs create -o canmount=off -o mountpoint=none rpool/ROOT
zfs create -o canmount=off -o mountpoint=none bpool/BOOT
- name: Create filesystem datasets for the root and boot filesystems
shell: |
zfs create -o mountpoint=/ \
-o com.ubuntu.zsys:bootfs=yes \
-o com.ubuntu.zsys:last-used=$(date +%s) rpool/ROOT/ubuntu
zfs create -o mountpoint=/boot bpool/BOOT/ubuntu
- name: Create datasets
shell: |
zfs create -o com.ubuntu.zsys:bootfs=no \
-o canmount=off rpool/ROOT/ubuntu/usr
zfs create -o com.ubuntu.zsys:bootfs=no \
-o canmount=off rpool/ROOT/ubuntu/var
zfs create rpool/ROOT/ubuntu/var/lib
zfs create rpool/ROOT/ubuntu/var/log
zfs create rpool/ROOT/ubuntu/var/spool
zfs create -o canmount=off -o mountpoint=/ rpool/USERDATA
zfs create -o com.ubuntu.zsys:bootfs-datasets=rpool/ROOT/ubuntu \
-o canmount=on -o mountpoint=/root rpool/USERDATA/root
chmod 700 /mnt/root
zfs create rpool/ROOT/ubuntu/var/lib/apt
zfs create rpool/ROOT/ubuntu/var/lib/dpkg
zfs create -o com.ubuntu.zsys:bootfs=no bpool/grub
- name: Mount a tmpfs at /run
shell: |
mkdir /mnt/run
mount -t tmpfs tmpfs /mnt/run
mkdir /mnt/run/lock
- name: Install the minimal system
shell: debootstrap noble /mnt
- name: Ensure directory exists
ansible.builtin.file:
path: /mnt/etc/zfs
state: directory
- name: Copy in zpool.cache
copy:
remote_src: true
src: /etc/zfs/zpool.cache
dest: /mnt/etc/zfs/zpool.cache
- debug: msg="{{ inventory_hostname_short }}"
- name: Set hostname
shell: |
hostname {{ inventory_hostname_short }}
hostname > /mnt/etc/hostname
- name: Replace a localhost entry with our own
ansible.builtin.lineinfile:
path: /mnt/etc/hosts
regexp: '^127\.0\.1\.1'
line: 127.0.1.1 {{ inventory_hostname_short }}
owner: root
group: root
mode: '0644'
- debug: msg="{{ ansible_facts.interfaces }}"
- name: Create netplan configuration
copy:
dest: /mnt/etc/netplan/01-netcfg.yaml
content: |
network:
version: 2
ethernets:
{% for interface in ansible_facts.interfaces %}
{{ interface }}:
dhcp4: true
dhcp6: true
{% endfor %}
- name: Create sources.lst
copy:
dest: /mnt/etc/apt/sources.list
content: |
deb http://archive.ubuntu.com/ubuntu noble main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu noble-updates main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu noble-backports main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu noble-security main restricted universe multiverse
- name: Bind the virtual filesystems from the LiveCD environment
shell: |
mount --make-private --rbind /dev /mnt/dev
mount --make-private --rbind /proc /mnt/proc
mount --make-private --rbind /sys /mnt/sys
- name: Download my key from GitHub
ansible.posix.authorized_key:
user: root
state: present
path: /mnt/root/.ssh/authorized_keys
key: https://github.com/mtelvers.keys
- name: Set the root password
shell: echo "root:{{ password }}" | chroot /mnt chpasswd
- name: Run apt update
shell: chroot /mnt apt update
- name: Install DOS FS tools
shell: chroot /mnt apt install -y dosfstools
- name: Format the boot partitions
shell: |
chroot /mnt mkdosfs -F 32 -s 1 -n EFI {{ item }}
with_items:
- "{{ part1 }}"
- name: Create EFI file system
shell: |
mkdir /mnt/boot/efi
echo /dev/disk/by-uuid/$(blkid -s UUID -o value {{ item }}) \
/boot/efi vfat defaults 0 0 >> /mnt/etc/fstab
chroot /mnt mount /boot/efi
with_items:
- "{{ part1 | first }}"
- name: Install grub
shell: chroot /mnt apt install -y grub-efi-amd64 grub-efi-amd64-signed \
linux-image-generic shim-signed zfs-initramfs zsys
- name: Remove OS prober
shell: chroot /mnt apt purge --yes os-prober
- name: Install mdadm and setup swap
shell: |
chroot /mnt apt install -y mdadm
chroot /mnt mdadm --create /dev/md0 --metadata=1.2 --level=raid5 \
--raid-devices={{ disks | length }} {{ part2 }}
chroot /mnt swapon -f /dev/md0
chroot /mnt echo /dev/disk/by-uuid/$(blkid -s UUID -o value /dev/md0) \
none swap discard 0 0 >> /etc/fstab
- name: Copy tmp.mount system unit
copy:
remote_src: true
src: /mnt/usr/share/systemd/tmp.mount
dest: /mnt/etc/systemd/system/tmp.mount
- name: Activate tmp.mount
shell: chroot /mnt systemctl enable tmp.mount
- name: Setup system groups
shell: chroot /mnt addgroup --system {{ item }}
with_items:
- lpadmin
- lxd
- sambashare
- name: Install SSHD
shell: chroot /mnt apt install -y openssh-server
- name: Update initrd files
shell: chroot /mnt update-initramfs -c -k all
- name: Update grub
shell: chroot /mnt update-grub
- name: Install GRUB to the ESP
shell: |
chroot /mnt grub-install --target=x86_64-efi --efi-directory=/boot/efi \
--bootloader-id=ubuntu --recheck --no-floppy
- name: Disable grub-initrd-fallback.service on raidz topology
shell: |
chroot /mnt systemctl mask grub-initrd-fallback.service
- name: Ensure directory exists
ansible.builtin.file:
path: /mnt/etc/zfs/zfs-list.cache
state: directory
- name: Ensure files exist
shell: |
touch /mnt/etc/zfs/zfs-list.cache/bpool
touch /mnt/etc/zfs/zfs-list.cache/rpool
- name: Run zed to build the cache
shell: |
chroot /mnt bash -c 'zed -F & ZED_PID=$! && zfs set canmount=on bpool/BOOT/ubuntu && zfs set canmount=on rpool/ROOT/ubuntu && sleep 30 && echo $ZED_PID && kill $ZED_PID'
- name: Fix file paths
shell: |
sed -Ei "s|/mnt/?|/|" /mnt/etc/zfs/zfs-list.cache/*
- name: Unmount everything
shell: |
mount | grep -v zfs | tac | awk '/\/mnt/ {print $3}' | \
xargs -i{} umount -lf {}
- name: Export the pool
shell: |
zpool export -a
- name: Reboot
shell: |
reboot
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment