Mercurial > hgrepos > FreeBSD > ports > sysutils > local-bsdtools
comparison sbin/ftjail @ 341:a204a7415d4a
"ftjail freebsd-update" is implemented.
It supports custom mountpoints also.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Sat, 03 Dec 2022 21:12:23 +0100 |
| parents | d3b5fe2712ca |
| children | 89877869a665 |
comparison
equal
deleted
inserted
replaced
| 340:d3b5fe2712ca | 341:a204a7415d4a |
|---|---|
| 42 | 42 |
| 43 copy-skel [-A] [-L] [-M MOUNTPOINT] [-P] [-u] SOURCE-DS SNAPSHOT-NAME TARGET-DS | 43 copy-skel [-A] [-L] [-M MOUNTPOINT] [-P] [-u] SOURCE-DS SNAPSHOT-NAME TARGET-DS |
| 44 | 44 |
| 45 build-etcupdate-current-tmpl DIRECTORY TARBALL | 45 build-etcupdate-current-tmpl DIRECTORY TARBALL |
| 46 | 46 |
| 47 freebsd-update [-k] [-o OLD-ORIGIN] DIRECTORY | 47 freebsd-update [-k] [-o OLD-ORIGIN] DIRECTORY NEW-ORIGIN [ETCUPDATE-TARBALL] |
| 48 | 48 |
| 49 ENVIRONMENT: | 49 ENVIRONMENT: |
| 50 | 50 |
| 51 All environment variables that affect "zfs" are effective also. | 51 All environment variables that affect "zfs" are effective also. |
| 52 | 52 |
| 872 fi | 872 fi |
| 873 } | 873 } |
| 874 | 874 |
| 875 | 875 |
| 876 #: | 876 #: |
| 877 #: Determine extra clone options with respect to the "mountpoint" property | |
| 878 #: | |
| 879 #: Args: | |
| 880 #: $1: the dataset | |
| 881 #: | |
| 882 #: Output (stdout) | |
| 883 #: The extra clone arguments | |
| 884 #: | |
| 885 #: Exit: | |
| 886 #: On unexpected source values | |
| 887 #: | |
| 888 _get_clone_extra_prop_for_mountpoint() { | |
| 889 local ds | |
| 890 | |
| 891 local _mp_name _mp_property _mp_value _mp_source | |
| 892 | |
| 893 ds="${1}" | |
| 894 | |
| 895 zfs get -H mountpoint "${ds}" \ | |
| 896 | { | |
| 897 IFS=$'\t' read -r _mp_name _mp_property _mp_value _mp_source | |
| 898 case "${_mp_source}" in | |
| 899 local) | |
| 900 echo -n "-o mountpoint=${_mp_value}" | |
| 901 ;; | |
| 902 default|inherited*) | |
| 903 ;; | |
| 904 temporary*|received*|'-'|none) | |
| 905 # XXX FIXME: Is this relevant on FreeBSD? | |
| 906 echo "ERROR: Unexpected SOURCE \"${_mp_source}\" for mountpoint at \`${_mp_value}'" 1>&2 | |
| 907 exit 1 | |
| 908 ;; | |
| 909 *) | |
| 910 echo "ERROR: Unexpected SOURCE for mountpoint property at \`${_mp_value}'" 1>&2 | |
| 911 exit 1; | |
| 912 ;; | |
| 913 esac | |
| 914 if [ "${_mp_value}" != "${_directory}" ]; then | |
| 915 echo "WARNING: dataset is not mounted at its configured mountpoint but elsewhere (probably via \"mount -t zfs\")" 1>&2 | |
| 916 fi | |
| 917 } | |
| 918 } | |
| 919 | |
| 920 | |
| 921 #: | |
| 922 #: Determine the "canmount" property for a dataset | |
| 923 #: | |
| 924 #: Args: | |
| 925 #: $1: the dataset | |
| 926 #: | |
| 927 #: Output (stdout): | |
| 928 #: The local value or "DEFAULT" for the (unset) default | |
| 929 #: | |
| 930 #: Exit: | |
| 931 #: On unexpected source values | |
| 932 #: | |
| 933 _get_canmount_setting_for_dataset() { | |
| 934 local ds | |
| 935 | |
| 936 local _cm_name _cm_property _cm_value _cm_source | |
| 937 | |
| 938 ds="${1}" | |
| 939 | |
| 940 zfs get -H canmount "${ds}" \ | |
| 941 | { | |
| 942 IFS=$'\t' read -r _cm_name _cm_property _cm_value _cm_source | |
| 943 case "${_cm_source}" in | |
| 944 local) | |
| 945 echo -n "canmount=${_cm_value}" | |
| 946 ;; | |
| 947 default) | |
| 948 echo -n "DEFAULT" | |
| 949 ;; | |
| 950 inherited|temporary*|received*|'-'|none) | |
| 951 # XXX FIXME: Is this relevant on FreeBSD? | |
| 952 echo "ERROR: Unexpected SOURCE \"${_cm_source}\" for canmount at \`${_cm_name}'" 1>&2 | |
| 953 exit 1 | |
| 954 ;; | |
| 955 *) | |
| 956 echo "ERROR: Unexpected SOURCE for canmount property at \`${_cm_name}'" 1>&2 | |
| 957 exit 1; | |
| 958 ;; | |
| 959 esac | |
| 960 } | |
| 961 } | |
| 962 | |
| 963 | |
| 964 #: | |
| 877 #: Implement the "freebsd-update" command for a thin jail | 965 #: Implement the "freebsd-update" command for a thin jail |
| 878 #: | 966 #: |
| 879 command_freebsd_update() { | 967 command_freebsd_update() { |
| 880 local _directory | 968 local _directory _new_origin _etcupdate_tarball |
| 881 local _opt_keep _opt_old_origin | 969 local _opt_keep _opt_old_origin |
| 882 | 970 |
| 883 local _res _jailname _dir_mounts _dir_fn_fstab _dir_basename | 971 local _res _jailname _dir_mounts _dir_fn_fstab _dir_basename _dir_fn_tldir |
| 884 local _root_dataset _root_mountpoint _root_type _root_options | 972 local _root_dataset _root_mountpoint _root_type _root_options |
| 885 local _dummy _opt | 973 local _clone_extra_props _canmount_prop |
| 974 local _line _opt | |
| 886 local _root_readonly _root_origin | 975 local _root_readonly _root_origin |
| 887 | 976 |
| 888 _opt_keep="no" | 977 _opt_keep="no" |
| 889 _opt_old_origin="" | 978 _opt_old_origin="" |
| 890 while getopts "ko:" _opt ; do | 979 while getopts "ko:" _opt ; do |
| 902 done | 991 done |
| 903 shift $((OPTIND-1)) | 992 shift $((OPTIND-1)) |
| 904 OPTIND=1 | 993 OPTIND=1 |
| 905 | 994 |
| 906 _directory="${1-}" | 995 _directory="${1-}" |
| 996 _new_origin="${2-}" | |
| 997 _etcupdate_tarball="${3-}" | |
| 907 | 998 |
| 908 [ -z "${_directory}" ] && { echo "ERROR: no directory given" 1>&2; return 2; } | 999 [ -z "${_directory}" ] && { echo "ERROR: no directory given" 1>&2; return 2; } |
| 909 [ -d "${_directory}" ] || { echo "ERROR: directory \`${_directory}' does not exist" 1>&2; exit 1; } | 1000 [ -d "${_directory}" ] || { echo "ERROR: directory \`${_directory}' does not exist" 1>&2; return 1; } |
| 1001 | |
| 1002 [ -z "${_new_origin}" ] && { echo "ERROR: no new origin given" 1>&2; return 2; } | |
| 1003 zfs list -H -o name -t snapshot "${_new_origin}" >/dev/null || { echo "ERROR: new origin does not exist" 1>&2; return 1; } | |
| 1004 if [ -n "${_etcupdate_tarball}" ]; then | |
| 1005 [ -f "${_etcupdate_tarball}" ] || { echo "ERROR: given etcupdate tarball does not exist " 1>&2; return 1; } | |
| 1006 fi | |
| 910 | 1007 |
| 911 _dir_basename="$(basename ${_directory})" | 1008 _dir_basename="$(basename ${_directory})" |
| 912 | 1009 |
| 913 set +e | 1010 set +e |
| 914 _jailname=$(_get_jail_from_path "${_directory}")´ | 1011 _jailname=$(_get_jail_from_path "${_directory}")´ |
| 915 _res=$? | 1012 _res=$? |
| 916 set -e | 1013 set -e |
| 917 if [ ${_res} -ne 2 ] ; then | 1014 if [ ${_res} -ne 2 ] ; then |
| 918 if [ ${_res} -ne 0 ] ; then | 1015 if [ ${_res} -ne 0 ] ; then |
| 919 exit ${_res} | 1016 return ${_res} |
| 920 else | 1017 else |
| 921 echo "ERROR: Please stop the \`${_jailname}' jail" >&2 | 1018 echo "ERROR: Please stop the \`${_jailname}' jail" >&2 |
| 922 exit 1 | 1019 return 1 |
| 923 fi | 1020 fi |
| 924 fi | 1021 fi |
| 925 _dir_mounts="$(_get_mounts_at_directory "${_directory}")" | 1022 _dir_mounts="$(_get_mounts_at_directory "${_directory}")" |
| 926 | 1023 |
| 927 # | 1024 # |
| 928 # Check preconditions thoroughly! | 1025 # Check preconditions thoroughly! |
| 929 # | 1026 # |
| 930 # Check that the first item/line is a read-only ZFS mount directly | 1027 # Check that the first item/line is a read-only ZFS mount directly |
| 931 # at the given directory. | 1028 # at the given directory. This must also be its configured |
| 1029 # mountpoint in ZFS. | |
| 932 # Also check that it is a clone proper. | 1030 # Also check that it is a clone proper. |
| 933 # | 1031 # |
| 934 IFS=' '$'\t' read -r _root_dataset _root_mountpoint _root_type _root_options _dummy <<EOF4tHGCSS | 1032 IFS=' '$'\t' read -r _root_dataset _root_mountpoint _root_type _root_options _line <<EOF4tHGCSS |
| 935 ${_dir_mounts} | 1033 ${_dir_mounts} |
| 936 EOF4tHGCSS | 1034 EOF4tHGCSS |
| 937 [ "${_root_mountpoint}" != "${_directory}" ] && { echo "ERROR: found root mountpoint does not match given directory" 1>&2; exit 1; } | 1035 [ "${_root_mountpoint}" != "${_directory}" ] && { echo "ERROR: found root mountpoint does not match given directory" 1>&2; return 1; } |
| 938 [ "${_root_type}" != "zfs" ] && { echo "ERROR: root mountpoint is not from a ZFS dataset" 1>&2; exit 1; } | 1036 [ "${_root_type}" != "zfs" ] && { echo "ERROR: root mountpoint is not from a ZFS dataset" 1>&2; return 1; } |
| 939 _root_readonly="$(zfs list -H -o readonly "${_root_dataset}")" | 1037 _root_readonly="$(zfs list -H -o readonly "${_root_dataset}")" |
| 940 [ "${_root_readonly}" != "on" ] && { echo "ERROR: the root dataset is not mounted read-only" 1>&2; exit 1; } | 1038 [ "${_root_readonly}" != "on" ] && { echo "ERROR: the root dataset is not mounted read-only" 1>&2; return 1; } |
| 941 _root_origin="$(zfs list -H -o origin "${_root_dataset}")" | 1039 _root_origin="$(zfs list -H -o origin "${_root_dataset}")" |
| 942 if [ -n "${_opt_old_origin}" ]; then | 1040 if [ -n "${_opt_old_origin}" ]; then |
| 943 [ "${_opt_old_origin}" != "${_root_origin}" ] && { echo "ERROR: origin mismatch" 1>&2; exit 1; } | 1041 [ "${_opt_old_origin}" != "${_root_origin}" ] && { echo "ERROR: origin mismatch" 1>&2; return 1; } |
| 944 else | 1042 else |
| 945 [ "${_root_origin}" = '-' ] && { echo "ERROR: the root dataset is not a ZFS clone" 1>&2; exit 1; } | 1043 [ "${_root_origin}" = '-' ] && { echo "ERROR: the root dataset is not a ZFS clone" 1>&2; return 1; } |
| 946 fi | 1044 fi |
| 1045 | |
| 1046 # Determine we need to clone with a custom (non inherited) "mountpoint" | |
| 1047 _clone_extra_props="$(_get_clone_extra_prop_for_mountpoint "${_root_dataset}") " | |
| 1048 # Determine we need to clone with a custom (non inherited) "canmount" | |
| 1049 _canmount_prop="$(_get_canmount_setting_for_dataset "${_root_dataset}")" | |
| 1050 | |
| 947 # | 1051 # |
| 948 # XXX FIXME: should we check that _root_options equals "ro" or | 1052 # XXX FIXME: should we check that _root_options equals "ro" or |
| 949 # start with "ro," | 1053 # start with "ro," |
| 950 # _root_origin="$(zfs list -H -o origin "${_root_dataset}")" | 1054 # _root_origin="$(zfs list -H -o origin "${_root_dataset}")" |
| 951 | 1055 |
| 952 _dir_fn_fstab="$(env TMPDIR=/var/tmp mktemp -t ftjail-fstab.${_dir_basename})" | 1056 _dir_fn_fstab="$(env TMPDIR=/var/tmp mktemp -t ftjail_${_dir_basename}.fstab)" |
| 953 echo -n "${_dir_mounts}" >>"${_dir_fn_fstab}" | 1057 echo -n "${_dir_mounts}" >>"${_dir_fn_fstab}" |
| 954 | 1058 |
| 1059 _dir_fn_tldir="$(env TMPDIR=/var/tmp mktemp -t ftjail_${_dir_basename}.tldir)" | |
| 1060 find "${_directory}" -depth 1 -type d 2>/dev/null | sort >>"${_dir_fn_tldir}" | |
| 1061 | |
| 955 # Unmount in reverse order: unmount can do it for us | 1062 # Unmount in reverse order: unmount can do it for us |
| 956 umount -a -F "${_dir_fn_fstab}" -v || exit 1 | 1063 echo "Unmounting all datasets mounted at \`${_directory}'" |
| 1064 umount -a -F "${_dir_fn_fstab}" -v | |
| 957 | 1065 |
| 958 # | 1066 # |
| 959 # XXX TBD: Hooks to create some new top-level dirs (/srv /proc et | 1067 # XXX TBD: Hooks to create some new top-level dirs (/srv /proc et |
| 960 # al.) if needed: clone RW, mount, make the dirs, | 1068 # al.) if needed: clone RW, mount, make the dirs, |
| 961 # umount, make the clone RO and continue "normally" by | 1069 # umount, make the clone RO and continue "normally" by |
| 962 # completely mounting the stored fstab. | 1070 # completely mounting the stored fstab. |
| 963 # | 1071 # |
| 964 | 1072 |
| 1073 # | |
| 1074 # Destroy the current read-only root clone and make a new clone based | |
| 1075 # on the given new origin. | |
| 1076 # The new clone temporarily is RW and is not to be mounted automatically. | |
| 1077 # These both properties are set again below after the new base is | |
| 1078 # adjusted properly. | |
| 1079 # | |
| 1080 echo "Destroying the cloned root dataset \`${_root_dataset}'" | |
| 1081 zfs destroy -v "${_root_dataset}" | |
| 1082 echo "Cloning a new root dataset \`${_root_dataset}' from new origin \`${_new_origin}'" | |
| 1083 zfs clone -o readonly=off -o canmount=noauto ${_clone_extra_props} "${_new_origin}" "${_root_dataset}" | |
| 1084 # | |
| 1085 # NOTE: Always mount with "mount -t zfs" because a custom | |
| 1086 # mountpoint is not reflected in the "mountpoint" | |
| 1087 # property. So in scripts to be sure to unmount and re-mount | |
| 1088 # at the same location always use "mount -t zfs". | |
| 1089 # | |
| 1090 echo "Remounting only the root dataset at \`${_directory}'" | |
| 1091 [ ! -d "${_directory}" ] && mkdir "${_directory}" | |
| 1092 mount -t zfs "${_root_dataset}" "${_directory}" | |
| 1093 # | |
| 1094 # Re-create all currently missing top-level dirs (aka mountpoint) | |
| 1095 # in the new clone. Most probably they serve as mountpoints for other | |
| 1096 # datasets. | |
| 1097 # | |
| 1098 # XXX FIXME: Re-create the current mode bits and/or ACLs also. | |
| 1099 # But most probably they are set properly in the mounted | |
| 1100 # datasets. | |
| 1101 # | |
| 1102 echo "Recreating missing top-level directories" | |
| 1103 cat "${_dir_fn_tldir}" \ | |
| 1104 | { | |
| 1105 while IFS='' read -r _line ; do | |
| 1106 if [ ! -d "${_line}" ]; then | |
| 1107 echo "Recreating top-level directory: ${_line}" | |
| 1108 mkdir "${_line}" | |
| 1109 fi | |
| 1110 done | |
| 1111 } | |
| 1112 echo "Unmounting the new root dataset" | |
| 1113 umount "${_directory}" | |
| 1114 echo "Re-setting some ZFS properties on the new cloned dataset" | |
| 1115 zfs set readonly=on "${_root_dataset}" | |
| 1116 # | |
| 1117 # Copy "canmount" properly last because it has been set to "noauto" | |
| 1118 # temporarily. | |
| 1119 # | |
| 1120 if [ -n "${_canmount_prop}" ]; then | |
| 1121 if [ "${_canmount_prop}" = "DEFAULT" ]; then | |
| 1122 # | |
| 1123 # "zfs inherit" is not possible for "canmount". | |
| 1124 # Use "inherit -S" to simulate a reset to "default" somewhat | |
| 1125 # | |
| 1126 # See also: https://github.com/openzfs/zfs/issues/5733 | |
| 1127 # | |
| 1128 zfs inherit -S canmount "${_root_dataset}" | |
| 1129 else | |
| 1130 zfs set "${_canmount_prop}" "${_root_dataset}" | |
| 1131 fi | |
| 1132 fi | |
| 1133 | |
| 1134 # Mount again | |
| 1135 echo "Mounting all datasets rooted at \`${_directory}'" | |
| 1136 [ ! -d "${_directory}" ] && mkdir "${_directory}" | |
| 1137 mount -a -F "${_dir_fn_fstab}" -v | |
| 1138 | |
| 1139 # Update configs | |
| 1140 if [ -n "${_etcupdate_tarball}" ]; then | |
| 1141 echo "Calling etcupdate for DESTDIR=${_directory}" | |
| 1142 etcupdate -D "${_directory}" -t "${_etcupdate_tarball}" | |
| 1143 fi | |
| 1144 | |
| 965 if [ "${_opt_keep}" != "yes" ]; then | 1145 if [ "${_opt_keep}" != "yes" ]; then |
| 1146 echo "Cleaning up...""" | |
| 1147 [ -n "${_dir_fn_tldir}" } && [ -f "${_dir_fn_tldir}" ] && rm -f "${_dir_fn_tldir}" | |
| 966 [ -n "${_dir_fn_fstab}" ] && [ -f "${_dir_fn_fstab}" ] && rm -f "${_dir_fn_fstab}" | 1148 [ -n "${_dir_fn_fstab}" ] && [ -f "${_dir_fn_fstab}" ] && rm -f "${_dir_fn_fstab}" |
| 967 fi | 1149 fi |
| 1150 echo "Done." | |
| 968 } | 1151 } |
| 969 | 1152 |
| 970 | 1153 |
| 971 # | 1154 # |
| 972 # Global option handling | 1155 # Global option handling |
