#!/bin/sh -e # Run as root [ "$(id -u)" -eq 0 ] || exec sudo "${0}" "${@}" cd "$(dirname "${0}")" meh() { printf " \033[1;32m*\033[0m %s\n" "${*}"; } omg() { printf " \033[1;33m*\033[0m %s\n" "${*}"; } wtf() { printf " \033[1;31m*\033[0m %s\n" "${*}"; kill $$; exit 1; } # Return the zpool name for a given dataset zfs_ds_pool() { [ "${1}" ] || return 1 echo "${1%%/*}" } # Make sure the datasets have the same pool zfs_is_same_pool() { [ "${1}" -a "${2}" ] || return 1 [ "$(zfs_ds_pool "${1}")" = "$(zfs_ds_pool "${2}")" ] } # Translate a mountpoint into its containing dataset name zfs_mount_to_ds() { [ "${1}" ] || return 1 zfs list -H -o name "${1}" } # Returns the mountpoint for a given dataset zfs_ds_mount() { [ "${1}" ] || return 1 zfs get -H -o value mountpoint "${1}" } # Make sure the passed directory corresponds exactly # with its containing dataset's mountpoint zfs_is_mountpoint() { [ "${1}" ] || return 1 ds="$(zfs_mount_to_ds "${1}")" [ "${ds}" ] || return 1 mp="$(zfs_ds_mount "${ds}")" [ "${mp}" = "${1}" ] } # Fetch the origin snapshot for a given dataset zfs_ds_origin() { [ "${1}" ] || return 1 zfs get -H -o value origin "${1}" } # Fetch the origin dataset for a given dataset zfs_ds_origin_ds() { [ "${1}" ] || return 1 sn="$(zfs_ds_origin "${1}")" echo "${sn%%@*}" } # List all snapshots for a given dataset, sorted by creation time zfs_ds_snapshots() { [ "${1}" ] || return 1 zfs list -rHt snapshot -d 1 -o name -s creation "${1}" | sed -e 's/^[^@]*@//' } # Get creation time in seconds since epoch for given dataset zfs_ds_creation() { [ "${1}" ] || return 1 zfs get -H -p -o value creation "${1}" } zfs_tests() { set +e -x zfs_is_same_pool "mtumishi/srv/ports/upstream/ports" "mtumishi/devel/ports"; echo $? zfs_mount_to_ds "/usr/ports" zfs_mount_to_ds "/usr/ports/misc" zfs_ds_mount "mtumishi/devel/ports" zfs_is_mountpoint "/usr/ports"; echo $? zfs_is_mountpoint "/usr/ports/misc"; echo $? zfs_ds_origin "mtumishi/devel/ports" zfs_ds_origin_ds "mtumishi/devel/ports" zfs_ds_snapshots "mtumishi/srv/ports/upstream/ports" zfs_ds_creation "mtumishi/devel/ports" set +x -e } ports_tree_valid() { [ "${1}" ] || return 1 [ -f "${1}/COPYRIGHT" -a -f "${1}/Makefile" -a -d "${1}/Mk" ] } overlays() { ( cd "${overlay_fs}" ls -1 | while read ovl do [ -d "${ovl}" -a -d "${ovl}/ports" -a -d "${ovl}/patch" ] || continue echo "${ovl}" done ) } #zfs_tests #exit # Location of overlays (Defaults to directory containing this script) overlay_fs="$(realpath "$(dirname "${0}")")" # Target ports tree (Defaults to ../assembled/ports) ports_fs="$(realpath "${overlay_fs}/../assembled/ports")" # Upstream ports tree (Defaults to ../upstream/ports) upstream_fs="$(realpath "${overlay_fs}/../upstream/ports")" # Dataset to hold temporary datasets (defaults to dataset holding overlays) temp_ds="$(zfs_mount_to_ds "${ports_fs}/..")" prep_ds="${temp_ds}/prepare" # Compute upstream dataset name from upstream filesystem zfs_is_mountpoint "${upstream_fs}" || { echo "Upstream filesystem ${upstream_fs} is not a ZFS dataset" >&2; exit 1; } upstream_ds="$(zfs_mount_to_ds "${upstream_fs}")" [ "${upstream_ds}" ] || { echo "Unable to get dataset for ${upstream_fs}" >&2; exit 1; } # Compute target ports dataset name; synthesize a new one if nonexistent if zfs_is_mountpoint "${ports_fs}" then ports_ds="$(zfs_mount_to_ds "${ports_fs}")" [ "$(zfs_ds_origin_ds "${ports_ds}")" = "${upstream_ds}" ] || wtf "Ports dataset is not a descendent of upstream dataset" else # Compute a temporary dataset name for ports tree ports_ds="${temp_ds}/liveports" create_ports=true omg "Unable to get dataset for ${ports_fs}; ports tree will be placed here instead:" omg " ${ports_ds}" omg "Make sure you move it to where you want it afterwards!" fi # Get most recent upstream snapshot; whine if it's more than 24 hours old upstream_snap="$(zfs_ds_snapshots "${upstream_ds}" | tail -n 1)" [ "${upstream_snap}" ] || wtf "No snapshots available for ${upstream_ds}" upstream_snap_age="$(( $(date +%s) - $(zfs_ds_creation "${upstream_ds}@${upstream_snap}") ))" [ "${upstream_snap_age}" -le 86400 ] || omg "Snapshot is stale! (${upstream_snap_age} seconds)" # Make sure the snapshot actually looks like a ports tree # Disabled because sauce is giving 'File name too long' now that it is a jail. #ports_tree_valid "${upstream_fs}/.zfs/snapshot/${upstream_snap}" || wtf "This sure is an odd-looking ports tree... ${upstream_fs}" # Make sure nothing is / [ "${ports_fs}" -a "${ports_fs}" != '/' ] || wtf "ports_fs is / ?!" [ "${upstream_fs}" -a "${upstream_fs}" != '/' ] || wtf "usptream_fs is / ?!" # All tests pass! Now perform the overlay meh Clone from ${upstream_ds}@${upstream_snap} zfs clone "${upstream_ds}@${upstream_snap}" "${prep_ds}" || wtf "Clone failed" zfs set atime=off "${prep_ds}" || wtf "atimes failed" prep_fs="$(zfs_ds_mount "${prep_ds}")" rm "${prep_fs}/.portsnap.INDEX" overlays | while read ovl do meh "=> Applying ${ovl} <=" meh Overlay ( cd "${overlay_fs}/${ovl}/ports"; find * | cpio -pR root:wheel "${prep_fs}" ) || wtf "overlay failed" meh Patch for patch in "${overlay_fs}/${ovl}/patch"/*.patch do meh "... ${patch##*/}" ( cd "${prep_fs}"; patch -p0 ) < "${patch}" || wtf "patch failed" done done meh Install zfs set readonly=on "${prep_ds}" || wtf "readonly failed" # Remember and unmount any null-mounts of the filesystem that is about to be replaced umount_and_generate_mount_lines() { mount | grep '(nullfs, ' | grep "^${ports_fs} " | while read null_src null_on null_dst null_opt do # Figure out if mount is read-only mount_flags="" case "${null_opt}" in *read-only*) mount_flags="-r" esac # Craft a mount line echo "echo '${null_dst}'; /sbin/mount -t nullfs ${mount_flags} '${null_src}' '${null_dst}';" meh "Unmounting ${null_dst}" >&2 /sbin/umount "${null_dst}" done } remount_cmd="$(umount_and_generate_mount_lines)" # Half of this is conditional on the old ports tree's existence [ "${create_ports}" ] || zfs set mountpoint=legacy "${ports_ds}" || wtf "failed hiding old ports" zfs set mountpoint="${ports_fs}" "${prep_ds}" || wtf "failed mounting new ports" [ "${create_ports}" ] || zfs destroy "${ports_ds}" || wtf "failed destroying old ports" zfs rename "${prep_ds}" "${ports_ds}" || wtf "failed renaming ports" # Remount null-mounts of this filesystem [ "${remount_cmd}" ] && meh "Remounting filesystems" eval "${remount_cmd}" echo "All done!"