3 # From <https://git.launchpad.net/ubiquity/plain/scripts/zsys-setup>
5 # This script creates ZFS pools and dataset compatible with zsys
9 # bpool/BOOT/ubuntu_${UUID}
10 # rpool/ROOT/ubuntu_${UUID}
11 # rpool/ROOT/ubuntu_${UUID}/var -o canmount=off
12 # rpool/ROOT/ubuntu_${UUID}/var/games
13 # rpool/ROOT/ubuntu_${UUID}/var/lib
14 # rpool/ROOT/ubuntu_${UUID}/var/lib/AccountsService
15 # rpool/ROOT/ubuntu_${UUID}/var/lib/apt
16 # rpool/ROOT/ubuntu_${UUID}/var/lib/dpkg
17 # rpool/ROOT/ubuntu_${UUID}/var/log
18 # rpool/ROOT/ubuntu_${UUID}/var/mail
19 # rpool/ROOT/ubuntu_${UUID}/var/snap
20 # rpool/ROOT/ubuntu_${UUID}/var/spool
21 # rpool/ROOT/ubuntu_${UUID}/var/www
22 # rpool/ROOT/ubuntu_${UUID}/var/lib/NetworkManager
23 # rpool/ROOT/ubuntu_${UUID}/srv
24 # rpool/ROOT/ubuntu_${UUID}/usr -o canmount=off
25 # rpool/ROOT/ubuntu_${UUID}/usr/local
26 # rpool/USERDATA/$user_$UUID2
27 # rpool/USERDATA/root_$UUID2
30 # - Verify that /target is mounted
33 # - delete all the partitions but the ESP
34 # - Create p1 ext4 size 100MB
35 # - Create p2 zfs bpool 1GB
36 # - Create p3 zfs rbool 100% remaining
38 # - Create /swapfile on /target
40 # After setup is done leave it mounted to let Ubiquity proceed with installation
44 REQUIREDPKGS
="zfsutils-linux"
46 ESP
="${TARGET}/boot/efi"
47 ZSYSTMP
="/tmp/$(basename $0)"
48 INIT_FLAG
="${ZSYSTMP}/init.done"
49 FSTAB_PARTMAN
="${ZSYSTMP}/fstab.partman"
50 PARTITION_LAYOUT
="${ZSYSTMP}/layout"
55 # Display script usage
57 Usage: $(basename "$0") [COMMAND] [OPTIONS...]
58 Prepares a zsys compatible ZFS system.
61 layout Get layout to display before formatting to ubiquity. Give the chosen disk as argument
62 init Initialize the pools and datasets
63 finalize Finalize the installation after the system has been installed
66 -d, --debug Enable debug mode
74 TEMP
=$
(getopt
-o $SHORTOPTS --long $LONGOPTS -- "$@")
92 COMMAND
=$
( echo $1|
tr '[:upper:]' '[:lower:]' )
98 check_prerequisites
() {
99 # Check and set requirements to run this script
101 # Check and set the requirements to run this test. If any of the
102 # requirement is missing the programs exit with error
105 # $@: List of required packages
108 # Exit program is a requirement is not met
109 echo "I: Checking system requirements"
111 if [ $
(id
-u) -ne 0 ]; then
112 echo "E: Script must be executed as root. Exiting!"
117 if ! dpkg-query
-W -f'${Status}' "${pkg}"|
grep -q "install ok installed" 2>/dev
/null
; then
118 echo "E: $pkg is required and not installed on this system. Exiting!"
128 if ! grep -qE "\s${target}\s" /proc
/mounts
; then
129 echo "E: $target is not mounted. Exiting!"
133 # Save fstab generated by partman
134 if [ -f "${target}/etc/fstab" ]; then
135 echo "I: Saving existing fstab"
136 cp "${target}/etc/fstab" "${FSTAB_PARTMAN}"
138 echo "W: ${target}/etc/fstab doesn't exist"
142 # It may fail to umount because the swap is being created by partman and not finished when we reach this point.
143 # Give it some time and retry with a sleep between tries.
147 for mountpoint
in "${ESP}" "${target}"; do
148 if [ ! -d "${mountpoint}" ]; then
152 echo "I: umounting ${mountpoint}"
154 # Do not make it quiet. We want to know why it failed.
155 if ! sudo umount
"${mountpoint}"; then
157 echo "W: Try ${iter}. Failed to umount ${mountpoint}."
158 if [ ${iter} -eq ${maxiter} ]; then
159 echo "E: Failed to umount ${mountpoint}. Exiting!"
171 # Returns disk, base name of the partition and partition numbers to create
175 if [ -z "${disk}" ]; then
176 # The entire disk has been formatted with use_device
177 # There is either one ext4 partition or one ext4 and one ESP
178 part
="$(grep -E "\s
${target}\s
" /proc/mounts | awk '{print $1}')"
181 if [ -n "${part}" ]; then
182 disk
="$(lsblk -lns -o TYPE,PATH ${part}| grep disk| awk '{print $2}')"
183 if [ -z "${disk}" ]; then
184 echo "E: Couldn't identify disk for partition ${part}. Exiting!"
187 # Some disks have letters in the partition number like /dev/nvme0n1p1
188 # In this case we want to retrieve 'p' so we deal only with partition number
189 # in the rest of the script and prepend the base.
190 partbase
="$(echo ${part} | sed -e 's/[0-9]*$//' | sed -e "s
#${disk}##")"
193 # The only purpose of this code is to display a friendly message in ubiquity to show the user
194 # what partitioning will be performed. However, on first call, the disk is not yet partitioned
195 # and collecting the information about disk partitioning would require to query partman. But we
196 # don't want to add this extra complexity just to display a message. Instead we hardcode the
197 # extension of the partition name depending on the type of disk, basically it's 'p' for anything
198 # else than standard drives (eg nvme01pX)
200 /dev
/sd
*|
/dev
/hd
*|
/dev
/vd
*)
209 if is_gpt
"${disk}"; then
210 # No extended partition on EFI + GPT
220 # MBR pools are on extended partition
232 echo "OK|${disk}|${partbase}|${partesp}|${partswap}|${partbpool}|${partrpool}"
242 partswap
=$
(( partbpool
- 1 ))
243 partext
=$
(( partesp
+ 1 ))
244 partprefix
="${disk}${partbase}"
246 sfdisktmp
="${ZSYSTMP}/sfdisk.cfg"
249 echo "I: Formatting disk $disk with partitions ESP:${partesp} ext:${partext} swap:${partswap} bpool:${partbpool} rpool:${partrpool}"
251 # bpool size: 500M < 5% of ZFS allocated space < 2G
252 # partswap is partition 2 on GPT systems and first extended partition on MBR.
253 size_percent
=$
(expr \
( $
(blockdev
--getsize64 ${partprefix}${partswap}) / 1024 / 1024 \
) \
* 5 / 100)
255 [ ${size_percent} -gt ${bpool_size} ] && bpool_size=${size_percent}
256 [ ${bpool_size} -gt 2048 ] && bpool_size
=2048
258 if is_gpt
"${disk}"; then
259 # Improvement: Delete all the partitions but the ESP
260 # There should be only 1 or 2 partitions but it can be made generic
261 if ! esp_exists
"${disk}"; then
262 start
=$
(sfdisk
-l "${disk}"|grep "^${partprefix}${partesp}"|awk '{print $2}')
263 cat > "${sfdisktmp}" <<EOF
264 ${partprefix}${partesp} : start= ${start}, size= 512M, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, bootable
267 sfdisk --delete "${disk}" ${partswap}
270 cat >> "${sfdisktmp}" <<EOF
271 ${partprefix}${partswap} : size= ${ss}M, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F
272 ${partprefix}${partbpool} : size= ${bpool_size}M, type=6A82CB45-1DD2-11B2-99A6-080020736631
273 ${partprefix}${partrpool} : type=6A85CF4D-1DD2-11B2-99A6-080020736631
276 if ! esp_exists "${disk}"; then
277 start=$(sfdisk -l "${disk}"|grep "^${partprefix}${partesp}"|awk '{print $2}')
278 cat > "${sfdisktmp}" <<EOF
279 ${partprefix}${partesp} : start= ${start}, size= 512M, type=ef, bootable
282 sfdisk --delete "${disk}" ${partswap}
285 cat >> "${sfdisktmp}" <<EOF
286 ${partprefix}${partswap} : size= ${ss}M, type=82
287 ${partprefix}${partbpool} : size= ${bpool_size}M, type=a5
288 ${partprefix}${partrpool} : type=a5
292 cat "${sfdisktmp}" | sfdisk --append "${disk}"
294 # Force a re-read of the partition table
295 echo "I: Re-reading partition table"
296 partx --add "${disk}" 2>/dev/null || true
297 partx --show "${disk}"
305 echo "I: Initializing ZFS"
306 # Now we can create the pools and dataset
307 UUID_ORIG=$(head -100 /dev/urandom | tr -dc 'a-z0-9
' |head -c6)
314 -O acltype=posixacl \
317 -O normalization=formD \
322 -O mountpoint=/ -R "${target}" rpool "${partrpool}"
325 # The version of bpool is set to the default version to prevent users from upgrading
326 # Then only features supported by grub are enabled.
330 -o feature@async_destroy=enabled \
331 -o feature@bookmarks=enabled \
332 -o feature@embedded_data=enabled \
333 -o feature@empty_bpobj=enabled \
334 -o feature@enabled_txg=enabled \
335 -o feature@extensible_dataset=enabled \
336 -o feature@filesystem_limits=enabled \
337 -o feature@hole_birth=enabled \
338 -o feature@large_blocks=enabled \
339 -o feature@lz4_compress=enabled \
340 -o feature@spacemap_histogram=enabled \
342 -O acltype=posixacl \
345 -O normalization=formD \
348 -O mountpoint=/boot -R "${target}" bpool "${partbpool}"
350 # Root and boot dataset
351 zfs create rpool/ROOT -o canmount=off -o mountpoint=none
352 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}" -o mountpoint=/
353 zfs create bpool/BOOT -o canmount=off -o mountpoint=none
354 zfs create "bpool/BOOT/ubuntu_${UUID_ORIG}" -o mountpoint=/boot
357 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var" -o canmount=off
358 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/lib"
359 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/lib/AccountsService"
360 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/lib/apt"
361 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/lib/dpkg"
362 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/lib/NetworkManager"
364 # Desktop specific system dataset
365 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/srv"
366 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/usr" -o canmount=off
367 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/usr/local"
368 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/games"
369 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/log"
370 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/mail"
371 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/snap"
372 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/spool"
373 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/www"
376 # Dataset associated to the user are created by the installer.
377 zfs create rpool/USERDATA -o canmount=off -o mountpoint=/
379 # Set zsys properties
380 zfs set com.ubuntu.zsys:bootfs='yes' "rpool/ROOT/ubuntu_${UUID_ORIG}"
381 zfs set com.ubuntu.zsys:last-used=$(date +%s) "rpool/ROOT/ubuntu_${UUID_ORIG}"
382 zfs set com.ubuntu.zsys:bootfs='no
' "rpool/ROOT/ubuntu_${UUID_ORIG}/srv"
383 zfs set com.ubuntu.zsys:bootfs='no
' "rpool/ROOT/ubuntu_${UUID_ORIG}/usr"
384 zfs set com.ubuntu.zsys:bootfs='no
' "rpool/ROOT/ubuntu_${UUID_ORIG}/var"
393 echo "I: Creating user $user with home $userhome"
394 mv "${target}/${userhome}" "${target}/tmp/home/${user}"
395 zfs create "rpool/USERDATA/${user}_${uuid}" -o canmount=on -o mountpoint=${userhome}
396 chown $(chroot "${target}" id -u ${user}):$(chroot ${target} id -g ${user}) "${target}/${userhome}"
397 rsync -a "${target}/tmp/home/${user}/" "${target}/${userhome}"
398 bootfsdataset=$(grep "\s${target}\s" /proc/mounts | awk '{ print
$1 }')
399 zfs set com.ubuntu.zsys:bootfs-datasets="${bootfsdataset}" rpool/USERDATA/${user}_${UUID_ORIG}
402 init_system_partitions() {
408 mkdir -p "${target}/boot/efi"
409 mount -t vfat "${partefi}" "${target}/boot/efi"
410 mkdir -p "${target}/boot/efi/grub"
412 echo "I: Mount grub directory"
413 # Finalize grub directory
414 mkdir -p "${target}/boot/grub"
415 mount -o bind "${target}/boot/efi/grub" "${target}/boot/grub"
419 if is_gpt "${1}"; then
420 parttype="C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
422 # FIXME: Currently partman-auto set the type of EFI on MBR as W95 (b) instead of EFI (ef)
425 sfdisk -d "${1}" | grep -q "type=${parttype}"
429 sfdisk -d "${1}" | awk '/^label
:/ {print
$2}'|grep -q gpt
432 check_prerequisites ${REQUIREDPKGS}
434 echo "I: Running $(basename "$0") ${COMMAND}"
436 if [ -z "${COMMAND}" ]; then
437 echo "E: ${COMMAND} is mandatory. Exiting!"
439 elif [ "${COMMAND}" = "layout" ]; then
440 # Just displays de layout that will be created without any change to the disk.
441 # At this stage we don't now yet the size of the partition that will be created.
442 IFS
="|" read ERR DISK PARTBASE PARTESP PARTSWAP PARTBPOOL PARTRPOOL
<<EOF
443 $(get_layout ${TARGET} "${EXTRAARG}")
446 if [ "${ERR}" != "OK" ]; then
451 cat > "${PARTITION_LAYOUT}" <<EOF
454 if ! esp_exists
"${DISK}"; then
455 cat >> "${PARTITION_LAYOUT}" <<EOF
456 part:vfat:ESP:${DISK}${PARTBASE}${PARTESP}
460 cat >> "${PARTITION_LAYOUT}" <<EOF
461 part:swap:swap:${DISK}${PARTBASE}${PARTSWAP}
462 part:zfs:bpool:${DISK}${PARTBASE}${PARTBPOOL}
463 part:zfs:rpool:${DISK}${PARTBASE}${PARTRPOOL}
466 elif [ "${COMMAND}" = "init" ]; then
469 IFS
="|" read ERR DISK PARTBASE PARTESP PARTSWAP PARTBPOOL PARTRPOOL
<<EOF
470 $(get_layout ${TARGET} "")
473 if [ "${ERR}" != "OK" ]; then
478 echo "I: Partition table before init of ZFS"
479 partx
--show "${DISK}"
481 # Swap files are not supported on ZFS, we use a swap partition instead:
482 SWAPFILE
="$(grep "^
${TARGET}" /proc/swaps |awk '{print $1}')"
483 # Give us a minimum swap partition size of 4MB in case we decide on
484 # no swap, just to keep the partition layout stable:
487 # Disable swap and get the swap volume size:
488 if [ -n "${SWAPFILE}" ]; then
489 SWAPSIZE
=$
(stat
-c%s
"${SWAPFILE}")
490 echo "I: Found swapfile with size ${SWAPSIZE}. Disabling"
491 swapoff
"${SWAPFILE}"
493 # Convert to MiB to align the size on the size of a block
494 SWAPVOLSIZE
=$
(( SWAPSIZE
/ 1024 / 1024 ))
496 prepare_target
"${TARGET}"
497 format_disk
"${DISK}" "${PARTBASE}" "${PARTESP}" "${PARTBPOOL}" "${PARTRPOOL}" "${SWAPVOLSIZE}"
498 init_zfs
"${TARGET}" "${DISK}${PARTBASE}${PARTBPOOL}" "${DISK}${PARTBASE}${PARTRPOOL}"
499 init_system_partitions
"${TARGET}" "${DISK}${PARTBASE}1" "${DISK}${PARTBASE}${PARTESP}"
502 # $TARGET/etc has been destroyed by the creation of the zfs partitition
504 mkdir
-p "${TARGET}/etc"
505 if [ -f "${FSTAB_PARTMAN}" ]; then
506 echo "I: Creating fstab"
507 grep -Ev '\s/\s|/swapfile' "${FSTAB_PARTMAN}" > "${TARGET}/etc/fstab"
510 if ! grep -q "boot/efi" "${TARGET}/etc/fstab"; then
511 espuuid
=$
(blkid
-s UUID
-o value
"${DISK}${PARTBASE}${PARTESP}")
512 echo "UUID=${espuuid}\t/boot/efi\tvfat\tumask=0022,fmask=0022,dmask=0022\t0\t1" >> "${TARGET}/etc/fstab"
515 # Bind mount grub from ESP to the expected location
516 echo "/boot/efi/grub\t/boot/grub\tnone\tdefaults,bind\t0\t0" >> "${TARGET}/etc/fstab"
518 if [ -n "${SWAPFILE}" ]; then
519 SWAPDEVICE
="${DISK}${PARTBASE}${PARTSWAP}"
520 mkswap
-f "${SWAPDEVICE}"
521 SWAPID
=$
(blkid
-s UUID
-o value
"${SWAPDEVICE}")
522 printf "UUID=${SWAPID}\tnone\tswap\tdiscard\t0\t0\n" >> "${TARGET}/etc/fstab"
523 swapon
-v "${SWAPDEVICE}"
525 # Make /boot/{grub,efi} world readable
526 sed -i 's#\(.*boot/efi.*\)umask=0077\(.*\)#\1umask=0022,fmask=0022,dmask=0022\2#' "${TARGET}/etc/fstab"
528 echo "I: Marking ZFS utilities to be kept in the target system"
529 apt-install zfsutils-linux
2>/dev
/null
530 apt-install zfs-initramfs
2>/dev
/null
531 apt-install zsys
2>/dev
/null
534 elif [ "${COMMAND}" = "finalize" ]; then
535 if [ ! -f "$INIT_FLAG" ]; then
536 echo "W: zsys init didn't succeed. Not proceeding with command: ${COMMAND}. Aborting!"
540 # Activate zfs generator.
541 # After enabling the generator we should run zfs set canmount=on DATASET
542 # in the chroot for one dataset of each pool to refresh the zfs cache.
543 echo "I: Activating zfs generator"
544 ln -s /usr
/lib
/zfs-linux
/zed.d
/history_event-zfs-list-cacher.sh
"${TARGET}/etc/zfs/zed.d"
547 zpool
set cachefile
= bpool
548 zpool
set cachefile
= rpool
549 cp /etc
/zfs
/zpool.cache
"${TARGET}/etc/zfs/"
550 mkdir
-p "${TARGET}/etc/zfs/zfs-list.cache"
551 touch "${TARGET}/etc/zfs/zfs-list.cache/bpool" "${TARGET}/etc/zfs/zfs-list.cache/rpool"
554 UUID_ORIG
=$
(head -100 /dev
/urandom |
tr -dc 'a-z0-9' |
head -c6)
555 mkdir
-p "${TARGET}/tmp/home"
556 for user
in ${TARGET}/home
/*; do
557 if [ -d "${user}" ]; then
558 user
="$(basename $user)"
559 move_user
"${TARGET}" "${user}" "/home/${user}" "${UUID_ORIG}"
563 move_user
"${TARGET}" root
/root
"${UUID_ORIG}"
565 echo "I: Changing sync mode of rpool to standard"
566 zfs
set sync
=standard rpool
568 echo "I: ZFS setup complete"
570 echo "E: Unknown command: $COMMAND"