modified: myjupyterlab.sh
[GalaxyCodeBases.git] / etc / Server / zsys-setup
blob8db24c2eb7e424a09a80d43dc7c6ea5d94862c3b
1 #!/bin/sh
4 # This script creates ZFS pools and dataset compatible with zsys
7 # Layout:
8 # bpool/BOOT/ubuntu_${UUID}
9 # rpool/ROOT/ubuntu_${UUID}
10 # rpool/ROOT/ubuntu_${UUID}/var -o canmount=off
11 # rpool/ROOT/ubuntu_${UUID}/var/games
12 # rpool/ROOT/ubuntu_${UUID}/var/lib
13 # rpool/ROOT/ubuntu_${UUID}/var/lib/AccountsService
14 # rpool/ROOT/ubuntu_${UUID}/var/lib/apt
15 # rpool/ROOT/ubuntu_${UUID}/var/lib/dpkg
16 # rpool/ROOT/ubuntu_${UUID}/var/log
17 # rpool/ROOT/ubuntu_${UUID}/var/mail
18 # rpool/ROOT/ubuntu_${UUID}/var/snap
19 # rpool/ROOT/ubuntu_${UUID}/var/spool
20 # rpool/ROOT/ubuntu_${UUID}/var/www
21 # rpool/ROOT/ubuntu_${UUID}/var/lib/NetworkManager
22 # rpool/ROOT/ubuntu_${UUID}/srv
23 # rpool/ROOT/ubuntu_${UUID}/usr -o canmount=off
24 # rpool/ROOT/ubuntu_${UUID}/usr/local
25 # rpool/USERDATA/$user_$UUID2
26 # rpool/USERDATA/root_$UUID2
28 # Steps:
29 # - Verify that /target is mounted
30 # - Retrieve fstab
31 # - unmount /target
32 # - delete all the partitions but the ESP
33 # - Create p1 ext4 size 100MB
34 # - Create p2 zfs bpool 1GB
35 # - Create p3 zfs rbool 100% remaining
36 # - Create datasets
37 # - Create /swapfile on /target
39 # After setup is done leave it mounted to let Ubiquity proceed with installation
41 set -eu
43 REQUIREDPKGS="zfsutils-linux"
44 TARGET="/target"
45 ESP="${TARGET}/boot/efi"
46 ZSYSTMP="/tmp/$(basename $0)"
47 INIT_FLAG="${ZSYSTMP}/init.done"
48 FSTAB_PARTMAN="${ZSYSTMP}/fstab.partman"
49 PARTITION_LAYOUT="${ZSYSTMP}/layout"
51 mkdir -p "${ZSYSTMP}"
53 usage() {
54 # Display script usage
55 cat<<EOF
56 Usage: $(basename "$0") [COMMAND] [OPTIONS...]
57 Prepares a zsys compatible ZFS system.
59 Commands:
60 layout Get layout to display before formatting to ubiquity. Give the chosen disk as argument
61 init Initialize the pools and datasets
62 finalize Finalize the installation after the system has been installed
63 Options:
64 -h, --help This help
65 -d, --debug Enable debug mode
66 EOF
67 exit
70 SHORTOPTS="hd"
71 LONGOPTS="help,debug"
73 TEMP=$(getopt -o $SHORTOPTS --long $LONGOPTS -- "$@")
74 eval set -- "$TEMP"
76 while true ; do
77 case "$1" in
78 -h|--help)
79 usage;;
80 -d|--debug)
81 set -x
82 shift;;
83 --)
84 shift;
85 break;;
87 usage;;
88 esac
89 done
91 COMMAND=$( echo $1| tr '[:upper:]' '[:lower:]' )
92 EXTRAARG=""
93 if [ $# -gt 1 ]; then
94 EXTRAARG="${2}"
97 check_prerequisites() {
98 # Check and set requirements to run this script
100 # Check and set the requirements to run this test. If any of the
101 # requirement is missing the programs exit with error
103 # Args:
104 # $@: List of required packages
106 # Returns
107 # Exit program is a requirement is not met
108 echo "I: Checking system requirements"
110 if [ $(id -u) -ne 0 ]; then
111 echo "E: Script must be executed as root. Exiting!"
112 exit 1
115 for pkg in $@; do
116 if ! dpkg-query -W -f'${Status}' "${pkg}"|grep -q "install ok installed" 2>/dev/null; then
117 echo "E: $pkg is required and not installed on this system. Exiting!"
118 exit 1
120 done
124 prepare_target() {
125 target="$1"
127 if ! grep -qE "\s${target}\s" /proc/mounts; then
128 echo "E: $target is not mounted. Exiting!"
129 exit 1
132 # Save fstab generated by partman
133 if [ -f "${target}/etc/fstab" ]; then
134 echo "I: Saving existing fstab"
135 cp "${target}/etc/fstab" "${FSTAB_PARTMAN}"
136 else
137 echo "W: ${target}/etc/fstab doesn't exist"
140 # umount /target
141 # It may fail to umount because the swap is being created by partman and not finished when we reach this point.
142 # Give it some time and retry with a sleep between tries.
143 iter=0
144 maxiter=10
146 for mountpoint in "${ESP}" "${target}"; do
147 if [ ! -d "${mountpoint}" ]; then
148 continue
151 echo "I: umounting ${mountpoint}"
152 while :; do
153 # Do not make it quiet. We want to know why it failed.
154 if ! sudo umount "${mountpoint}"; then
155 iter=$(( iter + 1 ))
156 echo "W: Try ${iter}. Failed to umount ${mountpoint}."
157 if [ ${iter} -eq ${maxiter} ]; then
158 echo "E: Failed to umount ${mountpoint}. Exiting!"
159 exit 1
161 sleep 3
162 else
163 break
165 done
166 done
169 get_layout() {
170 # Returns disk, base name of the partition and partition numbers to create
171 target="$1"
172 disk="$2"
174 if [ -z "${disk}" ]; then
175 # The entire disk has been formatted with use_device
176 # There is either one ext4 partition or one ext4 and one ESP
177 part="$(grep -E "\s${target}\s" /proc/mounts | awk '{print $1}')"
178 partbase=""
180 if [ -n "${part}" ]; then
181 disk="$(lsblk -lns -o TYPE,PATH ${part}| grep disk| awk '{print $2}')"
182 if [ -z "${disk}" ]; then
183 echo "E: Couldn't identify disk for partition ${part}. Exiting!"
184 exit 1
186 # Some disks have letters in the partition number like /dev/nvme0n1p1
187 # In this case we want to retrieve 'p' so we deal only with partition number
188 # in the rest of the script and prepend the base.
189 partbase="$(echo ${part} | sed -e 's/[0-9]*$//' | sed -e "s#${disk}##")"
191 else
192 # The only purpose of this code is to display a friendly message in ubiquity to show the user
193 # what partitioning will be performed. However, on first call, the disk is not yet partitioned
194 # and collecting the information about disk partitioning would require to query partman. But we
195 # don't want to add this extra complexity just to display a message. Instead we hardcode the
196 # extension of the partition name depending on the type of disk, basically it's 'p' for anything
197 # else than standard drives (eg nvme01pX)
198 case "${disk}" in
199 /dev/sd*|/dev/hd*|/dev/vd*)
200 partbase=""
203 partbase="p"
204 esac
207 partesp=1
208 if is_gpt "${disk}"; then
209 # No extended partition on EFI + GPT
210 # The layout is
211 # 1: ESP
212 # 2: swap
213 # 3: bpool
214 # 4: rpool
215 partswap=2
216 partbpool=3
217 partrpool=4
218 else
219 # MBR pools are on extended partition
220 # The layout is:
221 # 1: ESP
222 # 2: Extended
223 # 5: swap
224 # 6: bpool
225 # 7: rpool
226 partswap=5
227 partbpool=6
228 partrpool=7
231 echo "OK|${disk}|${partbase}|${partesp}|${partswap}|${partbpool}|${partrpool}"
234 format_disk() {
235 disk="$1"
236 partbase="$2"
237 partesp="$3"
238 partbpool="$4"
239 partrpool="$5"
240 ss="$6"
241 partswap=$(( partbpool - 1 ))
242 partext=$(( partesp + 1 ))
243 partprefix="${disk}${partbase}"
245 sfdisktmp="${ZSYSTMP}/sfdisk.cfg"
246 rm -f "${sfdisktmp}"
248 echo "I: Formatting disk $disk with partitions ESP:${partesp} ext:${partext} swap:${partswap} bpool:${partbpool} rpool:${partrpool}"
250 # bpool size: 500M < 5% of ZFS allocated space < 2G
251 # partswap is partition 2 on GPT systems and first extended partition on MBR.
252 size_percent=$(expr \( $(blockdev --getsize64 ${partprefix}${partswap}) / 1024 / 1024 \) \* 5 / 100)
253 bpool_size=500
254 [ ${size_percent} -gt ${bpool_size} ] && bpool_size=${size_percent}
255 [ ${bpool_size} -gt 2048 ] && bpool_size=2048
257 if is_gpt "${disk}"; then
258 # Improvement: Delete all the partitions but the ESP
259 # There should be only 1 or 2 partitions but it can be made generic
260 if ! esp_exists "${disk}"; then
261 start=$(sfdisk -l "${disk}"|grep "^${partprefix}${partesp}"|awk '{print $2}')
262 cat > "${sfdisktmp}" <<EOF
263 ${partprefix}${partesp} : start= ${start}, size= 512M, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, bootable
265 else
266 sfdisk --delete "${disk}" ${partswap}
269 cat >> "${sfdisktmp}" <<EOF
270 ${partprefix}${partswap} : size= ${ss}M, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F
271 ${partprefix}${partbpool} : size= ${bpool_size}M, type=6A82CB45-1DD2-11B2-99A6-080020736631
272 ${partprefix}${partrpool} : type=6A85CF4D-1DD2-11B2-99A6-080020736631
274 else
275 if ! esp_exists "${disk}"; then
276 start=$(sfdisk -l "${disk}"|grep "^${partprefix}${partesp}"|awk '{print $2}')
277 cat > "${sfdisktmp}" <<EOF
278 ${partprefix}${partesp} : start= ${start}, size= 512M, type=ef, bootable
280 else
281 sfdisk --delete "${disk}" ${partswap}
284 cat >> "${sfdisktmp}" <<EOF
285 ${partprefix}${partswap} : size= ${ss}M, type=82
286 ${partprefix}${partbpool} : size= ${bpool_size}M, type=a5
287 ${partprefix}${partrpool} : type=a5
291 cat "${sfdisktmp}" | sfdisk --append "${disk}"
293 # Force a re-read of the partition table
294 echo "I: Re-reading partition table"
295 partx --add "${disk}" 2>/dev/null || true
296 partx --show "${disk}"
299 init_zfs() {
300 target="$1"
301 partbpool="$2"
302 partrpool="$3"
304 echo "I: Initializing ZFS"
305 # Now we can create the pools and dataset
306 UUID_ORIG=$(head -100 /dev/urandom | tr -dc 'a-z0-9' |head -c6)
308 # Pools
309 # rpool
310 zpool create -f \
311 -o ashift=12 \
312 -O compression=lz4 \
313 -O acltype=posixacl \
314 -O xattr=sa \
315 -O relatime=on \
316 -O normalization=formD \
317 -O mountpoint=/ \
318 -O canmount=off \
319 -O dnodesize=auto \
320 -O sync=disabled \
321 -O mountpoint=/ -R "${target}" rpool "${partrpool}"
323 # bpool
324 # The version of bpool is set to the default version to prevent users from upgrading
325 # Then only features supported by grub are enabled.
326 zpool create -f \
327 -o ashift=12 \
328 -d \
329 -o feature@async_destroy=enabled \
330 -o feature@bookmarks=enabled \
331 -o feature@embedded_data=enabled \
332 -o feature@empty_bpobj=enabled \
333 -o feature@enabled_txg=enabled \
334 -o feature@extensible_dataset=enabled \
335 -o feature@filesystem_limits=enabled \
336 -o feature@hole_birth=enabled \
337 -o feature@large_blocks=enabled \
338 -o feature@lz4_compress=enabled \
339 -o feature@spacemap_histogram=enabled \
340 -O compression=lz4 \
341 -O acltype=posixacl \
342 -O xattr=sa \
343 -O relatime=on \
344 -O normalization=formD \
345 -O canmount=off \
346 -O devices=off \
347 -O mountpoint=/boot -R "${target}" bpool "${partbpool}"
349 # Root and boot dataset
350 zfs create rpool/ROOT -o canmount=off -o mountpoint=none
351 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}" -o mountpoint=/
352 zfs create bpool/BOOT -o canmount=off -o mountpoint=none
353 zfs create "bpool/BOOT/ubuntu_${UUID_ORIG}" -o mountpoint=/boot
355 # System dataset
356 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var" -o canmount=off
357 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/lib"
358 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/lib/AccountsService"
359 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/lib/apt"
360 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/lib/dpkg"
361 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/lib/NetworkManager"
363 # Desktop specific system dataset
364 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/srv"
365 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/usr" -o canmount=off
366 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/usr/local"
367 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/games"
368 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/log"
369 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/mail"
370 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/snap"
371 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/spool"
372 zfs create "rpool/ROOT/ubuntu_${UUID_ORIG}/var/www"
374 # USERDATA datasets
375 # Dataset associated to the user are created by the installer.
376 zfs create rpool/USERDATA -o canmount=off -o mountpoint=/
378 # Set zsys properties
379 zfs set com.ubuntu.zsys:bootfs='yes' "rpool/ROOT/ubuntu_${UUID_ORIG}"
380 zfs set com.ubuntu.zsys:last-used=$(date +%s) "rpool/ROOT/ubuntu_${UUID_ORIG}"
381 zfs set com.ubuntu.zsys:bootfs='no' "rpool/ROOT/ubuntu_${UUID_ORIG}/srv"
382 zfs set com.ubuntu.zsys:bootfs='no' "rpool/ROOT/ubuntu_${UUID_ORIG}/usr"
383 zfs set com.ubuntu.zsys:bootfs='no' "rpool/ROOT/ubuntu_${UUID_ORIG}/var"
386 move_user () {
387 target="$1"
388 user="$2"
389 userhome="$3"
390 uuid="$4"
392 echo "I: Creating user $user with home $userhome"
393 mv "${target}/${userhome}" "${target}/tmp/home/${user}"
394 zfs create "rpool/USERDATA/${user}_${uuid}" -o canmount=on -o mountpoint=${userhome}
395 chown $(chroot "${target}" id -u ${user}):$(chroot ${target} id -g ${user}) "${target}/${userhome}"
396 rsync -a "${target}/tmp/home/${user}/" "${target}/${userhome}"
397 bootfsdataset=$(grep "\s${target}\s" /proc/mounts | awk '{ print $1 }')
398 zfs set com.ubuntu.zsys:bootfs-datasets="${bootfsdataset}" rpool/USERDATA/${user}_${UUID_ORIG}
401 init_system_partitions() {
402 target="$1"
403 partefi="$2"
404 partgrub="$3"
406 # ESP
407 mkdir -p "${target}/boot/efi"
408 mount -t vfat "${partefi}" "${target}/boot/efi"
409 mkdir -p "${target}/boot/efi/grub"
411 echo "I: Mount grub directory"
412 # Finalize grub directory
413 mkdir -p "${target}/boot/grub"
414 mount -o bind "${target}/boot/efi/grub" "${target}/boot/grub"
417 esp_exists() {
418 if is_gpt "${1}"; then
419 parttype="C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
420 else
421 # FIXME: Currently partman-auto set the type of EFI on MBR as W95 (b) instead of EFI (ef)
422 parttype="b"
424 sfdisk -d "${1}" | grep -q "type=${parttype}"
427 is_gpt() {
428 sfdisk -d "${1}" | awk '/^label:/ {print $2}'|grep -q gpt
431 check_prerequisites ${REQUIREDPKGS}
433 echo "I: Running $(basename "$0") ${COMMAND}"
435 if [ -z "${COMMAND}" ]; then
436 echo "E: ${COMMAND} is mandatory. Exiting!"
437 exit 1
438 elif [ "${COMMAND}" = "layout" ]; then
439 # Just displays de layout that will be created without any change to the disk.
440 # At this stage we don't now yet the size of the partition that will be created.
441 IFS="|" read ERR DISK PARTBASE PARTESP PARTSWAP PARTBPOOL PARTRPOOL<<EOF
442 $(get_layout ${TARGET} "${EXTRAARG}")
445 if [ "${ERR}" != "OK" ]; then
446 echo "${ERR}"
447 exit 1
450 cat > "${PARTITION_LAYOUT}" <<EOF
451 disk:${DISK}
453 if ! esp_exists "${DISK}"; then
454 cat >> "${PARTITION_LAYOUT}" <<EOF
455 part:vfat:ESP:${DISK}${PARTBASE}${PARTESP}
459 cat >> "${PARTITION_LAYOUT}" <<EOF
460 part:swap:swap:${DISK}${PARTBASE}${PARTSWAP}
461 part:zfs:bpool:${DISK}${PARTBASE}${PARTBPOOL}
462 part:zfs:rpool:${DISK}${PARTBASE}${PARTRPOOL}
465 elif [ "${COMMAND}" = "init" ]; then
466 rm -f "${INIT_FLAG}"
468 IFS="|" read ERR DISK PARTBASE PARTESP PARTSWAP PARTBPOOL PARTRPOOL<<EOF
469 $(get_layout ${TARGET} "")
472 if [ "${ERR}" != "OK" ]; then
473 echo "${ERR}"
474 exit 1
477 echo "I: Partition table before init of ZFS"
478 partx --show "${DISK}"
480 # Swap files are not supported on ZFS, we use a swap partition instead:
481 SWAPFILE="$(grep "^${TARGET}" /proc/swaps |awk '{print $1}')"
482 # Give us a minimum swap partition size of 4MB in case we decide on
483 # no swap, just to keep the partition layout stable:
484 SWAPSIZE=4194304
486 # Disable swap and get the swap volume size:
487 if [ -n "${SWAPFILE}" ]; then
488 SWAPSIZE=$(stat -c%s "${SWAPFILE}")
489 echo "I: Found swapfile with size ${SWAPSIZE}. Disabling"
490 swapoff "${SWAPFILE}"
492 # Convert to MiB to align the size on the size of a block
493 SWAPVOLSIZE=$(( SWAPSIZE / 1024 / 1024 ))
495 prepare_target "${TARGET}"
496 format_disk "${DISK}" "${PARTBASE}" "${PARTESP}" "${PARTBPOOL}" "${PARTRPOOL}" "${SWAPVOLSIZE}"
497 init_zfs "${TARGET}" "${DISK}${PARTBASE}${PARTBPOOL}" "${DISK}${PARTBASE}${PARTRPOOL}"
498 init_system_partitions "${TARGET}" "${DISK}${PARTBASE}1" "${DISK}${PARTBASE}${PARTESP}"
500 # Generate fstab
501 # $TARGET/etc has been destroyed by the creation of the zfs partitition
502 # Recreate it
503 mkdir -p "${TARGET}/etc"
504 if [ -f "${FSTAB_PARTMAN}" ]; then
505 echo "I: Creating fstab"
506 grep -Ev '\s/\s|/swapfile' "${FSTAB_PARTMAN}" > "${TARGET}/etc/fstab"
509 if ! grep -q "boot/efi" "${TARGET}/etc/fstab"; then
510 espuuid=$(blkid -s UUID -o value "${DISK}${PARTBASE}${PARTESP}")
511 echo "UUID=${espuuid}\t/boot/efi\tvfat\tumask=0022,fmask=0022,dmask=0022\t0\t1" >> "${TARGET}/etc/fstab"
514 # Bind mount grub from ESP to the expected location
515 echo "/boot/efi/grub\t/boot/grub\tnone\tdefaults,bind\t0\t0" >> "${TARGET}/etc/fstab"
517 if [ -n "${SWAPFILE}" ]; then
518 SWAPDEVICE="${DISK}${PARTBASE}${PARTSWAP}"
519 mkswap -f "${SWAPDEVICE}"
520 SWAPID=$(blkid -s UUID -o value "${SWAPDEVICE}")
521 printf "UUID=${SWAPID}\tnone\tswap\tdiscard\t0\t0\n" >> "${TARGET}/etc/fstab"
522 swapon -v "${SWAPDEVICE}"
524 # Make /boot/{grub,efi} world readable
525 sed -i 's#\(.*boot/efi.*\)umask=0077\(.*\)#\1umask=0022,fmask=0022,dmask=0022\2#' "${TARGET}/etc/fstab"
527 echo "I: Marking ZFS utilities to be kept in the target system"
528 apt-install zfsutils-linux 2>/dev/null
529 apt-install zfs-initramfs 2>/dev/null
530 apt-install zsys 2>/dev/null
532 touch "$INIT_FLAG"
533 elif [ "${COMMAND}" = "finalize" ]; then
534 if [ ! -f "$INIT_FLAG" ]; then
535 echo "W: zsys init didn't succeed. Not proceeding with command: ${COMMAND}. Aborting!"
536 exit 1
539 # Activate zfs generator.
540 # After enabling the generator we should run zfs set canmount=on DATASET
541 # in the chroot for one dataset of each pool to refresh the zfs cache.
542 echo "I: Activating zfs generator"
543 ln -s /usr/lib/zfs-linux/zed.d/history_event-zfs-list-cacher.sh "${TARGET}/etc/zfs/zed.d"
545 # Create zpool cache
546 zpool set cachefile= bpool
547 zpool set cachefile= rpool
548 cp /etc/zfs/zpool.cache "${TARGET}/etc/zfs/"
549 mkdir -p "${TARGET}/etc/zfs/zfs-list.cache"
550 touch "${TARGET}/etc/zfs/zfs-list.cache/bpool" "${TARGET}/etc/zfs/zfs-list.cache/rpool"
552 # Handle userdata
553 UUID_ORIG=$(head -100 /dev/urandom | tr -dc 'a-z0-9' |head -c6)
554 mkdir -p "${TARGET}/tmp/home"
555 for user in ${TARGET}/home/*; do
556 if [ -d "${user}" ]; then
557 user="$(basename $user)"
558 move_user "${TARGET}" "${user}" "/home/${user}" "${UUID_ORIG}"
560 done
562 move_user "${TARGET}" root /root "${UUID_ORIG}"
564 echo "I: Changing sync mode of rpool to standard"
565 zfs set sync=standard rpool
567 echo "I: ZFS setup complete"
568 else
569 echo "E: Unknown command: $COMMAND"
570 exit 1