1 #!/bin/bash
2 # Init script for mkinitramfs
3 # Copyright (C) 2023 Aaron Ball <nullspoon@oper.io>
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 export DEBUG=0
19 export QUIET=0
20 export INTERACTIVE=0
21 export ROOTDEV=''
22 export MOUNTOPTS=()
23
24 screen_init() {
25 # Clear screen
26 [ "${QUIET}" -eq 1 ] && printf '\033c'
27
28 # Output message file if it exists
29 [ -f /etc/msg ] && printf '%s\n' "$(</etc/msg)"
30 }
31
32
33 log() {
34 local lvl="${1}"
35 local msg="${2}"
36 [ "${QUIET}" -eq 1 ] && [ "${lvl,,}" = "info" ] && return
37
38 printf -- "%s\n" "${msg}"
39 if [ "${DEBUG}" -ne 0 ]; then
40 echo "Press enter to continue"
41 read
42 fi
43 }
44
45
46 #
47 # Resolves the path to the root device from the cmdline root= syntax.
48 # Currently handles UUID syntax and /dev/sdx syntax, btrfs subvols, debugging,
49 # and quiet options.
50 #
51 parse_cmdline() {
52 local cmdline="$(<${1:?})"
53
54 for i in ${cmdline[@]}; do
55 case "${i}" in
56 root=*)
57 if [ "${i:5:4}" == 'UUID' ]; then
58 # mount by uuid
59 ROOTDEV="/dev/disk/by-uuid/${i##*=}"
60 elif [ "${i:5:5}" == 'LABEL' ]; then
61 # mount by label
62 ROOTDEV=/dev/disk/by-partlabel/${i##*=}
63 else
64 # mount by dev
65 ROOTDEV="${i##*=}"
66 fi
67 ;;
68 subvol=*)
69 MOUNTOPTS+=(-o subvol=${i#*=})
70 ;;
71 initdebug)
72 # Enable debug mode (this is gonna be slow)
73 DEBUG=1
74 ;;
75 interactive)
76 # Enable interactive mode
77 INTERACTIVE=1
78 ;;
79 quiet)
80 # Enable quiet boot (less output)
81 QUIET=1
82 ;;
83 esac
84 done
85 }
86
87
88 #
89 # Takes an encrypted device path and executes cryptsetup luksOpen. Returns path
90 # to the new decrypted block device. This path takes the rough form of
91 # /dev/mapper/_dev_sdx
92 #
93 setup_encrypted() {
94 local path="${1}"
95 local name="${path//\//_}"
96
97 # Decrypted block dev path is /dev/mapper/${name}
98 cryptsetup luksOpen ${path} ${name}
99
100 # Notify user and wait for input on failure
101 if [ "$?" -gt 0 ]; then
102 # Ensure decryption was successful, it not, drop to debug shell and reboot once done.
103 printf 'ERROR: Could not decrypt device\n' >&2
104 printf 'Dropping to debug shell (will automatically reboot on exit).\n' >&2
105 /bin/bash --norc -i
106 printf 'Rebooting\n' >&2
107 printf 'b' > /proc/sysrq-trigger
108 fi
109
110 # Probe the new crypto device for an additional partition table
111 partprobe "/dev/mapper/${name}" 2>/dev/null 1>/dev/null
112
113 # Success. Return the path of the decrypted root device
114 echo "/dev/mapper/${name}"
115 }
116
117
118 udev_populate() {
119 # Start udevd if not already running
120 pidof -s udevd > /dev/null || /sbin/udevd --daemon
121
122 /sbin/udevadm trigger --type=devices --action=add --subsystem-match=block
123 /sbin/udevadm trigger --type=devices --action=add --subsystem-match=input
124 /sbin/udevadm settle
125
126 # Stop udevd once devices are populated so main udevd can finish booting
127 kill $(pidof udevd)
128 }
129
130
131 #
132 # Main function to keep the main operations code nicely separated from the
133 # rest.
134 #
135 main() {
136 # Mount the /proc and /sys filesystems.
137 mount -t tmpfs -o mode=0755,nousid,nodev none /run
138 mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
139 mount -t proc -o nodev,noexec,nosuid proc /proc
140 mount -t tmpfs shm /dev/shm
141 mount -t devtmpfs -o exec,nosuid,noatime dev /dev
142 mount -t devpts devpts /dev/pts
143
144 local fakeroot='/mnt/root'
145
146 if [ ! -d "${fakeroot}" ]; then
147 log INFO "Fake root location ${fakeroot} does not exist. Cannot boot."
148 fi
149
150 parse_cmdline /proc/cmdline
151
152 # display fanciful boot image
153 screen_init
154 udev_populate
155
156 log INFO "Root device: ${ROOTDEV}"
157
158 # Drop to maintenance shell if root device does not exist.
159 if [ "$?" -ne 0 ]; then
160 log ERROR "ERROR: Root device ${ROOTDEV} does not exist or can not be accessed."
161 log ERROR " Dropping to maintenance shell for troubleshooting."
162 log ERROR " (You may just need to wait longer for the root device)"
163 /bin/bash -i
164 fi
165
166 # Modprobe atkbd to ensure user can type password
167 for i in i8042 atkbd; do
168 printf 'Loading required module [%s]\n' "${i}"
169 modprobe "${i}" || printf 'Failed loading %s\n' "${i}"
170 done
171
172 if cryptsetup isLuks "${ROOTDEV}"; then
173 # Set new rootdev location (/dev/mapper/something). This will update it to
174 # the decrypted block device path.
175 log INFO "Root device ${ROOTDEV} is encrypted."
176 ROOTDEV=$(setup_encrypted ${ROOTDEV})
177 log INFO "New rootdev: ${ROOTDEV}"
178 fi
179
180 # Drop to interactive shell if requested
181 if [ "${INTERACTIVE}" -eq 1 ]; then
182 log INFO "Interractive shell requested. Type 'exit' to continue boot sequence."
183 /bin/bash --norc
184 fi
185
186 # Mount the fakeroot.
187 log INFO "Mounting fakeroot"
188 mount -o ro ${MOUNTOPTS[@]} ${ROOTDEV} ${fakeroot}
189
190 # Ensure switch_root will be possible for destination fakeroot
191 if [ ! -f "${fakeroot}/sbin/init" ]; then
192 log ERROR "ERROR: Destination fakeroot ${fakeroot} does not have an init script."
193 log ERROR " Cannot proceed. Dropping to shell for troubleshooting."
194 /bin/bash -i
195 fi
196
197 # Boot the real McCoy
198 log INFO "Switching root"
199 exec switch_root ${fakeroot} /sbin/init
200
201 # This will be reached if previous command failed
202 log ERROR "There was an error performing switch_root to ${fakeroot}."
203 log ERROR "Starting recovery shell."
204 /bin/bash -i
205 }
206
207 main ${@}
|