#!/bin/sh # This script generates FreeBSD images for OpenNebula, being heavily inspired # from srht's FreeBSD build image definition. It assumes running on a FreeBSD host. # ZFS installation as documented by the FreeBSD project # https://wiki.freebsd.org/RootOnZFS/GPTZFSBoot set -e set -x # XXX: Handle command-line arguments? RELEASE=14.0-RELEASE ARCH=amd64 IMAGE_PATH_ZFS="freebsd-zfs-$RELEASE-$(date -I).img.qcow2" IMAGE_PATH_UFS="freebsd-ufs-$RELEASE-$(date -I).img.qcow2" IMAGE_SIZE=10G # Comment out to simply use latest version # Hash checking is disabled when specifying this #CLOUDSETUP_VERSION=1.2 DIST_BASE="https://download.freebsd.org/ftp/releases/$ARCH/$RELEASE" ZPOOL=zroot ZPOOL_TMP="zinstalling" ZFSTARGET="$(mktemp -d /var/tmp/zfsbuild.XXXXX)" UFSTARGET="$(mktemp -d /var/tmp/ufsbuild.XXXXX)" if zpool list -Ho name "$ZPOOL_TMP" 2>/dev/null; then echo "The pool $ZPOOL_TMP is already imported." >&2 exit 1 fi cleanup() { sync ||: umount "$UFSTARGET/dev" ||: umount "$UFSTARGET/tmp" ||: umount "$UFSTARGET/var/tmp" ||: umount "$UFSTARGET" ||: zpool export "$ZPOOL_TMP" ||: mdconfig -du md0 ||: mdconfig -du md1 ||: rm -rf "$CLOUDSETUP_WORK" ||: rmdir "$ZFSTARGET" ||: rmdir "$UFSTARGET" ||: } trap cleanup EXIT if [ "$(whoami)" != 'root' ]; then echo "This script must be run as root." >&2 exit 1 fi if ! command -v rsync >/dev/null then env ASSUME_ALWAYS_YES=YES pkg install -y rsync fi if ! command -v qemu-img >/dev/null then env ASSUME_ALWAYS_YES=YES pkg install -y qemu-tools fi portsnap fetch if [ -f /usr/ports/README ] then portsnap update || portsnap extract else portsnap extract fi if [ -n "$CLOUDSETUP_VERSION" ] then sed -i .bak -e '/^PORTVERSION=/ s/[0-9]*\.[0-9]*/'"$CLOUDSETUP_VERSION/" /usr/ports/sysutils/firstboot-cloudsetup/Makefile make -C /usr/ports/sysutils/firstboot-cloudsetup makesum fi make -C /usr/ports/sysutils/firstboot-cloudsetup clean package CLOUDSETUP_VERSION="$(fgrep VERSION /usr/ports/sysutils/firstboot-cloudsetup/Makefile | cut -f2- | tr -d \\t)" CLOUDSETUP_PKG="/usr/ports/sysutils/firstboot-cloudsetup/work/pkg/firstboot-cloudsetup-${CLOUDSETUP_VERSION}.pkg" tar -tzf "$CLOUDSETUP_PKG" >/dev/null # check that it's a valid tar, or we crash due to set -e # tar -t lists the contents of a tar file, but does not extract make -C /usr/ports/sysutils/firstboot-freebsd-update clean package FBUPDATE_VERSION="$(fgrep VERSION /usr/ports/sysutils/firstboot-freebsd-update/Makefile | cut -f2- | tr -d \\t)" FBUPDATE_PKG="/usr/ports/sysutils/firstboot-freebsd-update/work/pkg/firstboot-freebsd-update-${FBUPDATE_VERSION}.pkg" tar -tzf "$FBUPDATE_PKG" >/dev/null # check that it's a valid tar, or we crash due to set -e ufsdisk="$(mktemp /var/tmp/ufsdisk.XXXXX)" truncate -s 6G "$ufsdisk" mdconfig -a -t vnode -f "$ufsdisk" -u md1 gpart create -s gpt /dev/md1 #gpart add -t efi -l efiboot0 -s 260M md1 gpart add -t freebsd-boot -l gptboot -b 40 -s 512K md1 gpart bootcode -b /boot/pmbr -p /boot/gptboot -i 1 md1 gpart add -t freebsd-ufs -l rootfs -b 1M -s 5G md1 newfs -U /dev/md1p2 # Mount allocated image. mount /dev/md1p2 "$UFSTARGET" # Allocate and partition/format disk image. # We use "legacy boot", aka BIOS boot # Preferably, we'd use EFI boot here, check the FreeBSD wiki link in the header # to see how to make that change, but make the EFI partition larger zfsdisk="$(mktemp /var/tmp/zfsdisk.XXXXX)" truncate -s 6G "$zfsdisk" mdconfig -a -t vnode -f "$zfsdisk" -u md0 gpart create -s gpt /dev/md0 #gpart add -t efi -l efiboot0 -s 260M md1 gpart add -t freebsd-boot -l gptboot0 -b 40 -s 512K md0 gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 md0 gpart add -t freebsd-zfs -l zfs0 -b 1M -s 5G md0 zpool create -O compression=on -o ashift=12 -o "altroot=$ZFSTARGET" -m none -t "$ZPOOL_TMP" "$ZPOOL" md0p2 zfs create -o mountpoint=none "$ZPOOL_TMP/ROOT" # We set zstd-19 so our image will become smaller, at the cost of a longer build time. # At the end of the process, we disable zstd-19 again using zfs inherit compression, # but all files already written will remain zstd-19 compressed zfs create -o mountpoint=/ -o canmount=noauto "$ZPOOL_TMP/ROOT/default" mount -t zfs "$ZPOOL_TMP/ROOT/default" "$ZFSTARGET" zpool set "bootfs=$ZPOOL_TMP/ROOT/default" "$ZPOOL_TMP" zfs create -o mountpoint=/tmp -o exec=on -o setuid=off "$ZPOOL_TMP/tmp" zfs create -o canmount=off -o mountpoint=/usr "$ZPOOL_TMP/usr" zfs create "$ZPOOL_TMP/usr/home" zfs create -o exec=off -o setuid=off "$ZPOOL_TMP/usr/src" zfs create -o mountpoint=/usr/ports -o setuid=off "$ZPOOL_TMP/usr/ports" zfs create -o canmount=off -o mountpoint=/var "$ZPOOL_TMP/var" zfs create -o exec=off -o setuid=off "$ZPOOL_TMP/var/audit" zfs create -o exec=off -o setuid=off "$ZPOOL_TMP/var/crash" zfs create -o exec=off -o setuid=off "$ZPOOL_TMP/var/log" zfs create -o atime=on -o exec=off -o setuid=off "$ZPOOL_TMP/var/mail" zfs create -o exec=on -o setuid=off "$ZPOOL_TMP/var/tmp" ln -s /usr/home "$ZFSTARGET/home" chmod 1777 "$ZFSTARGET/var/tmp" chmod 1777 "$ZFSTARGET/tmp" # Download and extract base system. dist_files="kernel.txz base.txz" dist_dir="/usr/freebsd-dist/$ARCH/$RELEASE" mkdir -p "$dist_dir" for f in $dist_files do fetch -m -o "$dist_dir/$f" "$DIST_BASE/$f" tar -C "$UFSTARGET" -xJf "$dist_dir/$f" done # Mount dev and tmp in chroot mount -t devfs devfs "$UFSTARGET/dev" mount_nullfs /tmp "$UFSTARGET/tmp" mount_nullfs /var/tmp "$UFSTARGET/var/tmp" # Install the first-boot script that configures the network and ssh key # We must use --rootdir and not --chroot, because the file is read from within the chroot # --automatic means that the package is considered to be installed "automatically", # aka as a dependency of something, so pkg autoremove will remove it. # We do not run pkg autoremove ourselves, that's up to the administrator. pkg --rootdir "$UFSTARGET" add --automatic "$CLOUDSETUP_PKG" "$FBUPDATE_PKG" # Configure new system. touch "$UFSTARGET/firstboot" sysrc -f "$UFSTARGET/boot/loader.conf" \ zfs_load="YES" \ autoboot_delay="-1" \ sysrc -f "$UFSTARGET/etc/rc.conf" \ zfs_enable="YES" \ ntpd_enable="YES" \ sshd_enable="YES" \ growfs_enable="YES" \ hostname="freebsd" \ firstboot_cloudsetup_enable="YES" \ firstboot_freebsd_update_enable="YES" \ # The resolv.conf file is written by firstboot_cloudsetup #cp /etc/resolv.conf "$UFSTARGET/etc/resolv.conf" tzsetup -s -C "$UFSTARGET" UTC # Add PermitRootLogin without-password, unless PermitRootLogin yes was already set sed -i .orig -e '/^#PermitRootLogin[[:blank:]]/a\ PermitRootLogin without-password ' -e '/^PermitRootLogin[[:blank:]]*no/ s/\([[:blank:]]\).*$/\1without-password/' \ "$UFSTARGET/etc/ssh/sshd_config" if ! grep -Eq '^PermitRootLogin (without-password|yes)' "$UFSTARGET/etc/ssh/sshd_config" then cat >>"$UFSTARGET/etc/ssh/sshd_config" <"$ZFSTARGET/etc/fstab" printf '# Device\tMountpoint\tFStype\tOptions\t\tDump\tPass#\n%s\t%s\t\t%s\t%s\t%s\t%s\n' \ /dev/gpt/rootfs / ufs rw,noatime 1 1 \ >"$UFSTARGET/etc/fstab" sync ||: zfs inherit compression "$ZPOOL_TMP/ROOT/default" trap : EXIT cleanup mkdir -p "$ARCH" qemu-img convert -f raw -O qcow2 "$zfsdisk" "$ARCH/$IMAGE_PATH_ZFS" qemu-img convert -f raw -O qcow2 "$ufsdisk" "$ARCH/$IMAGE_PATH_UFS" rm "$zfsdisk" "$ufsdisk" # Filesystem will be enlarged by growfs(7) on next startup qemu-img resize "$ARCH/$IMAGE_PATH_ZFS" "$IMAGE_SIZE" qemu-img resize "$ARCH/$IMAGE_PATH_UFS" "$IMAGE_SIZE"