summaryrefslogtreecommitdiff
path: root/bin/setup-boot
blob: 47ac84d2164090b9070b228b26b37c648d7527b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#!/bin/sh
#
# setup-boot - Generate boot configuration for an installed system
# rawnix.org - https://rawnix.org
#
# Usage: setup-boot <mountpoint>
#        setup-boot /mnt
#
# Writes one file into the target:
#
#   /etc/limine-sync.conf   cmdline for the generated limine entries
#
# Encrypted volumes other than root are genluks's job -- it owns
# /etc/crypttab and /etc/cryptkeys. This only warns if it finds some
# and no crypttab exists.
#
# Run it after setup-filesystem and before installing the kernel.
# The kernel package's boot/ trigger fires limine-sync, which reads
# limine-sync.conf; with no cmdline the generated entries have no
# root= and the install boots to a panic.
#
# Everything here is derived from what is mounted under <mountpoint>
# right now, exactly as genfstab does it.  Mount the target the way it
# will be at runtime before running this.
#
# LUKS root
# ---------
# If the target's root is a /dev/mapper/<name> device, the mapping is
# traced back to its LUKS partition and the cmdline becomes
#
#   cryptroot=UUID=<luks uuid> root=/dev/mapper/<name> ro
#
# tinyrd creates the mapping under exactly the name in root=, so the
# name you chose with `cryptsetup open` is the name that must survive
# into the installed system.  Nothing else records it.

set -e

die()  { echo "ERROR: $*" >&2; exit 1; }
say()  { printf '>>> %s\n' "$*"; }
warn() { printf '  ! %s\n' "$*" >&2; }

[ "$(id -u)" = 0 ] || die "Must run as root"
[ -n "$1" ]        || die "Usage: setup-boot <mountpoint>"
[ -d "$1" ]        || die "Directory not found: $1"

MNT="${1%/}"
[ -n "$MNT" ] || MNT="/"

mountpoint -q "$MNT" || die "$MNT is not a mountpoint"
[ -d "$MNT/etc" ]    || die "$MNT/etc does not exist — run setup-filesystem $MNT first"

# The ESP not being mounted is the failure that looks like success:
# limine-sync refuses outright (good), but only at kernel-install time,
# several minutes and one download later.  Say so now.
if ! mountpoint -q "$MNT/boot/efi"; then
    warn "$MNT/boot/efi is not a mountpoint"
    warn "mount the ESP there before installing the kernel, or limine-sync will refuse"
else
    # A FAT partition with the wrong GPT type GUID takes the install all
    # the way to the end: mkfs works, the bootloader is copied, every
    # step reports success, and then firmware doing a spec-compliant
    # removable-media scan finds no partition of type ESP and boots
    # nothing.  Firmware booting from an NVRAM entry that names the
    # partition directly never notices, which is what makes this
    # survive testing on one machine.
    _esp=$(findmnt -n -o SOURCE --target "$MNT/boot/efi" | head -1)
    _pt=$(lsblk -n -o PARTTYPE "$_esp" 2>/dev/null | head -1)
    case "$_pt" in
        c12a7328-f81f-11d2-ba4b-00a0c93ec93b|C12A7328-F81F-11D2-BA4B-00A0C93EC93B) ;;
        "") warn "cannot read the partition type of $_esp — check it is an EFI System Partition" ;;
        *)  warn "$_esp is partition type $_pt, not an EFI System Partition"
            warn "some firmware will not boot it; fix with: sgdisk -t N:ef00 /dev/DISK" ;;
    esac
fi

# ── Root device ───────────────────────────────────────────────
ROOTDEV=$(findmnt -n -o SOURCE --target "$MNT" | head -1)
[ -n "$ROOTDEV" ] || die "cannot determine the root device of $MNT"

CRYPTLINE=""
MAPNAME=""
ROOTSPEC=""

case "$ROOTDEV" in
    /dev/mapper/*)
        MAPNAME=${ROOTDEV#/dev/mapper/}
        command -v cryptsetup >/dev/null 2>&1 || \
            die "$ROOTDEV is a device-mapper device but cryptsetup is not installed"

        cryptsetup status "$MAPNAME" >/dev/null 2>&1 || \
            die "cryptsetup does not know the mapping $MAPNAME"

        # "type: LUKS2" — a plain dm-crypt or an LVM volume has no
        # header for tinyrd to find, and would boot to the 30s timeout.
        _type=$(cryptsetup status "$MAPNAME" | awk '$1 == "type:" { print $2 }')
        case "$_type" in
            LUKS*) ;;
            *) die "$MAPNAME is $_type, not LUKS — tinyrd can only unlock LUKS" ;;
        esac

        BACKING=$(cryptsetup status "$MAPNAME" | awk '$1 == "device:" { print $2 }')
        [ -n "$BACKING" ] || die "cannot find the backing device of $MAPNAME"

        LUKSUUID=$(cryptsetup luksUUID "$BACKING")
        [ -n "$LUKSUUID" ] || die "cannot read the LUKS UUID of $BACKING"

        CRYPTLINE="cryptroot=UUID=$LUKSUUID "
        # The mapper name is stable by construction, so no UUID needed:
        # tinyrd creates the mapping under exactly this name.
        ROOTSPEC="$ROOTDEV"
        say "Encrypted root: $BACKING -> $MAPNAME ($LUKSUUID)"
        ;;
    *)
        # Prefer the filesystem UUID.  A device path is whatever the
        # kernel happened to call the disk during the install, and that
        # name is not a property of the disk: adding a drive, moving a
        # cable, or booting the same image with a different controller
        # renames it, and the installed system then stops at
        # "tinyrd: waiting for /dev/sdb2" for a partition that is
        # present under another name.  tinyrd resolves UUID= itself by
        # reading superblocks -- there is no blkid or udev that early.
        _uuid=$(blkid -s UUID -o value "$ROOTDEV" 2>/dev/null || true)
        if [ -n "$_uuid" ]; then
            ROOTSPEC="UUID=$_uuid"
            say "Plain root: $ROOTDEV ($ROOTSPEC)"
        else
            ROOTSPEC="$ROOTDEV"
            warn "cannot read a filesystem UUID from $ROOTDEV"
            warn "falling back to the device path, which breaks if the disk is renamed"
        fi
        ;;
esac

# ── limine-sync.conf ──────────────────────────────────────────
say "Writing $MNT/etc/limine-sync.conf"
cat > "$MNT/etc/limine-sync.conf" << EOF
# /etc/limine-sync.conf - generated by setup-boot
#
# Read by limine-sync when it regenerates the managed block of
# limine.conf on the ESP.  Keys are uppercase; an unrecognised one is
# warned about and ignored.  Other keys, with their defaults:
#
#   TITLE=rawnix
#   MICROCODE=amd-ucode.img
#   INITRD=tinyrd.img          (set empty to boot with no initramfs)
#   XEN_KERNEL=                (series pin, e.g. 6.12)
#
# Edit and re-run limine-sync to apply.  The hand-written part of the
# menu is /etc/limine.conf.in, a different file.

CMDLINE=${CRYPTLINE}root=$ROOTSPEC ro quiet
EOF

# ── crypttab ──────────────────────────────────────────────────
# Not written here. genluks owns /etc/crypttab and /etc/cryptkeys, and
# two tools writing one file means whichever runs last wins: setup-boot
# re-run after genluks would put every volume back to "none", and the
# first sign of it would be a boot prompting for volumes that used to
# open by themselves.
#
# So this only looks, and says what it sees.
if command -v cryptsetup >/dev/null 2>&1; then
    extras=""
    for src in $(findmnt -R -n -o SOURCE "$MNT" | sort -u); do
        case "$src" in /dev/mapper/*) ;; *) continue ;; esac
        name=${src#/dev/mapper/}
        [ "$name" != "$MAPNAME" ] || continue
        cryptsetup status "$name" >/dev/null 2>&1 || continue
        extras="$extras $name"
    done

    if [ -n "$extras" ]; then
        if [ -f "$MNT/etc/crypttab" ]; then
            say "Encrypted volumes besides root:$extras"
            say "  already listed in $MNT/etc/crypttab"
        else
            warn "encrypted volumes besides root:$extras"
            warn "no $MNT/etc/crypttab — they will not be opened at boot"
            warn "run: genluks $MNT"
        fi
    fi
fi

echo ""
say "Boot configuration ready:"
sed -n 's/^CMDLINE=/  cmdline: /p' "$MNT/etc/limine-sync.conf"
echo ""
echo "  Install the kernel last: its boot/ trigger runs limine-sync,"
echo "  which writes limine.conf on the ESP from what is in /boot."
echo ""