Clear Linux etc files.
[GalaxyCodeBases.git] / etc / Server / zsys-galaxy
blob223c09455f0d19d99ed2ea76dbff5581b38bd966
1 #!/bin/sh
3 # From <https://git.launchpad.net/ubiquity/plain/scripts/zsys-setup>
5 # This script creates ZFS pools and dataset compatible with zsys
8 # Layout:
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
29 # Steps:
30 # - Verify that /target is mounted
31 # - Retrieve fstab
32 # - unmount /target
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
37 # - Create datasets
38 # - Create /swapfile on /target
40 # After setup is done leave it mounted to let Ubiquity proceed with installation
42 set -eu
44 REQUIREDPKGS="zfsutils-linux"
45 TARGET="/target"
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"
52 mkdir -p "${ZSYSTMP}"
54 usage() {
55 # Display script usage
56 cat<<EOF
57 Usage: $(basename "$0") [COMMAND] [OPTIONS...]
58 Prepares a zsys compatible ZFS system.
60 Commands:
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
64 Options:
65 -h, --help This help
66 -d, --debug Enable debug mode
67 EOF
68 exit
71 SHORTOPTS="hd"
72 LONGOPTS="help,debug"
74 TEMP=$(getopt -o $SHORTOPTS --long $LONGOPTS -- "$@")
75 eval set -- "$TEMP"
77 while true ; do
78 case "$1" in
79 -h|--help)
80 usage;;
81 -d|--debug)
82 set -x
83 shift;;
84 --)
85 shift;
86 break;;
88 usage;;
89 esac
90 done
92 COMMAND=$( echo $1| tr '[:upper:]' '[:lower:]' )
93 EXTRAARG=""
94 if [ $# -gt 1 ]; then
95 EXTRAARG="${2}"
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
104 # Args:
105 # $@: List of required packages
107 # Returns
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!"
113 #exit 1
116 for pkg in $@; do
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!"
119 exit 1
121 done
125 prepare_target() {
126 target="$1"
128 if ! grep -qE "\s${target}\s" /proc/mounts; then
129 echo "E: $target is not mounted. Exiting!"
130 exit 1
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}"
137 else
138 echo "W: ${target}/etc/fstab doesn't exist"
141 # umount /target
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.
144 iter=0
145 maxiter=10
147 for mountpoint in "${ESP}" "${target}"; do
148 if [ ! -d "${mountpoint}" ]; then
149 continue
152 echo "I: umounting ${mountpoint}"
153 while :; do
154 # Do not make it quiet. We want to know why it failed.
155 if ! sudo umount "${mountpoint}"; then
156 iter=$(( iter + 1 ))
157 echo "W: Try ${iter}. Failed to umount ${mountpoint}."
158 if [ ${iter} -eq ${maxiter} ]; then
159 echo "E: Failed to umount ${mountpoint}. Exiting!"
160 exit 1
162 sleep 3
163 else
164 break
166 done
167 done
170 get_layout() {
171 # Returns disk, base name of the partition and partition numbers to create
172 target="$1"
173 disk="$2"
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}')"
179 partbase=""
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!"
185 exit 1
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}##")"
192 else
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)
199 case "${disk}" in
200 /dev/sd*|/dev/hd*|/dev/vd*)
201 partbase=""
204 partbase="p"
205 esac
208 partesp=1
209 if is_gpt "${disk}"; then
210 # No extended partition on EFI + GPT
211 # The layout is
212 # 1: ESP
213 # 2: swap
214 # 3: bpool
215 # 4: rpool
216 partswap=2
217 partbpool=3
218 partrpool=4
219 else
220 # MBR pools are on extended partition
221 # The layout is:
222 # 1: ESP
223 # 2: Extended
224 # 5: swap
225 # 6: bpool
226 # 7: rpool
227 partswap=5
228 partbpool=6
229 partrpool=7
232 echo "OK|${disk}|${partbase}|${partesp}|${partswap}|${partbpool}|${partrpool}"
235 format_disk() {
236 disk="$1"
237 partbase="$2"
238 partesp="$3"
239 partbpool="$4"
240 partrpool="$5"
241 ss="$6"
242 partswap=$(( partbpool - 1 ))
243 partext=$(( partesp + 1 ))
244 partprefix="${disk}${partbase}"
246 sfdisktmp="${ZSYSTMP}/sfdisk.cfg"
247 rm -f "${sfdisktmp}"
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)
254 bpool_size=500
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
266 else
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
275 else
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
281 else
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}"
300 init_zfs() {
301 target="$1"
302 partbpool="$2"
303 partrpool="$3"
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)
309 # Pools
310 # rpool
311 zpool create -f \
312 -o ashift=12 \
313 -O compression=lz4 \
314 -O acltype=posixacl \
315 -O xattr=sa \
316 -O relatime=on \
317 -O normalization=formD \
318 -O mountpoint=/ \
319 -O canmount=off \
320 -O dnodesize=auto \
321 -O sync=disabled \
322 -O mountpoint=/ -R "${target}" rpool "${partrpool}"
324 # bpool
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.
327 zpool create -f \
328 -o ashift=12 \
329 -d \
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 \
341 -O compression=lz4 \
342 -O acltype=posixacl \
343 -O xattr=sa \
344 -O relatime=on \
345 -O normalization=formD \
346 -O canmount=off \
347 -O devices=off \
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
356 # System dataset
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"
375 # USERDATA datasets
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"
387 move_user () {
388 target="$1"
389 user="$2"
390 userhome="$3"
391 uuid="$4"
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() {
403 target="$1"
404 partefi="$2"
405 partgrub="$3"
407 # ESP
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"
418 esp_exists() {
419 if is_gpt "${1}"; then
420 parttype="C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
421 else
422 # FIXME: Currently partman-auto set the type of EFI on MBR as W95 (b) instead of EFI (ef)
423 parttype="b"
425 sfdisk -d "${1}" | grep -q "type=${parttype}"
428 is_gpt() {
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!"
438 exit 1
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
447 echo "${ERR}"
448 exit 1
451 cat > "${PARTITION_LAYOUT}" <<EOF
452 disk:${DISK}
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
467 rm -f "${INIT_FLAG}"
469 IFS="|" read ERR DISK PARTBASE PARTESP PARTSWAP PARTBPOOL PARTRPOOL<<EOF
470 $(get_layout ${TARGET} "")
473 if [ "${ERR}" != "OK" ]; then
474 echo "${ERR}"
475 exit 1
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:
485 SWAPSIZE=4194304
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}"
501 # Generate fstab
502 # $TARGET/etc has been destroyed by the creation of the zfs partitition
503 # Recreate it
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
533 touch "$INIT_FLAG"
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!"
537 exit 1
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"
546 # Create zpool cache
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"
553 # Handle userdata
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}"
561 done
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"
569 else
570 echo "E: Unknown command: $COMMAND"
571 exit 1