#!/bin/sh # # genfstab - Generate /etc/fstab from current mounts # rawnix.org - https://rawnix.org # # Usage: genfstab [-d] # genfstab /mnt > /mnt/etc/fstab # # -d emit device paths (/dev/nvme0n1p2) instead of UUIDs # # UUIDs are the default. A device path in fstab is stable only # until the next disk is plugged in: adding a USB drive can renumber # /dev/sdX and the system boots into an emergency shell for a change # that had nothing to do with it. -d exists for the cases where the # device really is fixed and you want to read the file at a glance. set -e die() { echo "ERROR: $*" >&2; exit 1; } USE_UUID=1 while [ $# -gt 0 ]; do case "$1" in -d) USE_UUID=0; shift ;; -*) die "Unknown option: $1" ;; *) break ;; esac done [ -n "$1" ] || die "Usage: genfstab [-d] " [ -d "$1" ] || die "Mountpoint not found: $1" # Strip a trailing slash before it reaches the sed below. With # MNT=/mnt/ the substitution turns /mnt/boot/efi into "boot/efi" # with no leading slash, and the resulting fstab is silently wrong. MNT="${1%/}" [ -n "$MNT" ] || MNT="/" mountpoint -q "$MNT" || die "$MNT is not a mountpoint" # Resolve a device to UUID=..., falling back to the device path if # blkid cannot see it. Never emits a bare UUID= with nothing after. devspec() { _dev="$1" if [ "$USE_UUID" -eq 1 ]; then _uuid=$(blkid -o value -s UUID "$_dev" 2>/dev/null || true) if [ -n "$_uuid" ]; then printf 'UUID=%s' "$_uuid" return 0 fi echo "genfstab: no UUID for $_dev, using device path" >&2 fi printf '%s' "$_dev" } cat << 'EOF' # /etc/fstab: static file system information # rawnix.org - https://rawnix.org # # EOF # -l is not optional: without it findmnt draws a tree, and every # submount's TARGET arrives prefixed with box-drawing characters # ("|-/mnt/boot/efi"). The $MNT strip below then does not match, and # the entry lands in fstab as /mnt/boot/efi -- which mounts nothing at # boot and drops the installed system into an emergency shell. The # root line has no prefix, so root is correct and only the submounts # are wrong, which is what makes it look like a UUID problem. findmnt -l -R -n -o TARGET,SOURCE,FSTYPE "$MNT" | sort | while read target source fstype; do case "$fstype" in tmpfs|devtmpfs|devpts|sysfs|proc|cgroup*|efivarfs|hugetlbfs|mqueue|securityfs|pstore|bpf|fusectl|configfs) continue ;; esac case "$source" in /dev/loop*) continue ;; esac dir=${target#"$MNT"} [ -n "$dir" ] || dir="/" case "$dir" in /) opts="defaults,noatime" ; dump=1; pass=1 ;; /boot/efi) opts="defaults,noatime,nodev,noexec" ; dump=0; pass=2 ;; /tmp) opts="defaults,noatime,nosuid,nodev" ; dump=1; pass=2 ;; /home) opts="defaults,noatime" ; dump=1; pass=2 ;; /var) opts="defaults,noatime" ; dump=1; pass=2 ;; *) opts="defaults,noatime" ; dump=1; pass=2 ;; esac printf "%-42s %-20s %-9s %-31s %s %s\n" \ "$(devspec "$source")" "$dir" "$fstype" "$opts" "$dump" "$pass" done # Every swap signature the *running* system can see, which in an # installer is only the target's. Run from a live system that has its # own swap, this writes that swap into the target's fstab. for dev in $(blkid -t TYPE=swap -o device 2>/dev/null); do printf "%-42s %-20s %-9s %-31s %s %s\n" \ "$(devspec "$dev")" "swap" "swap" "defaults" "0" "0" done cat << 'EOF' devpts /dev/pts devpts noexec,nosuid,gid=tty,mode=0620 0 0 tmpfs /dev/shm tmpfs mode=1777,nosuid,nodev 0 0 EOF # efivarfs only on a machine booted through UEFI. Under legacy BIOS # there is no /sys/firmware/efi to mount it on: nofail covers a missing # device, not a missing mount point, so mount -a fails at boot and # stage 1 drops to the emergency shell. The installer runs on the # target machine, so its own /sys/firmware/efi says which firmware the # installed system will boot with. if [ -d /sys/firmware/efi ]; then echo 'efivarfs /sys/firmware/efi/efivars efivarfs nodev,noexec,nosuid,nofail 0 0' fi echo '# End of file'