blob: 36dde8056e6a75a2dc05c4ad3b655ce5d6b7e07d (
plain)
1 #!/bin/bash
2 # Init script for mkinitramfs
3 # Copyright (C) 2016 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 CRYPTROOT=''
23 export MOUNTOPTS=()
24
25 screen_init() {
26 # Clear screen
27 [ "${QUIET}" -eq 1 ] && clear
28
29 # Output message file if it exists
30 [ -f /etc/msg ] && cat /etc/msg
31 }
32
33
34 #
35 # Mounts the fakeroot from the kernel "root" param
36 #
37 mount_fakeroot() {
38 local rootdev=${1}
39 local fakeroot=${2}
40 shift && shift
41 local opts=(${@})
42
43 mount -o ro ${opts[@]} ${rootdev} ${fakeroot}
44 }
45
46
47 log() {
48 local lvl="${1}"
49 local msg="${2}"
50 [ "${QUIET}" -eq 1 ] && [ "${lvl,,}" = "info" ] && return
51
52 printf -- "%s\n" "${msg}"
53 if [ "${DEBUG}" -ne 0 ]; then
54 echo "Press enter to continue"
55 read
56 fi
57 }
58
59
60 #
61 # Resolves the path to the root device from the cmdline root= syntax.
62 # Currently handles UUID syntax and /dev/sdx syntax
63 #
64 parse_cmdline() {
65 local cmdline=${1}
66
67 for i in ${cmdline[@]}; do
68 case "${i}" in
69 root=*)
70 if [ "${i:5:4}" == 'UUID' ]; then
71 # mount by uuid
72 local uuid=$(echo ${i} | cut -d '=' -f 3)
73 ROOTDEV="$(blkid -U ${uuid})"
74 elif [ "${i:5:5}" == 'LABEL' ]; then
75 # mount by label
76 local label=$(echo ${i} | cut -d '=' -f 3)
77 ROOTDEV="$(blkid -L ${label})"
78 else
79 # mount by dev
80 ROOTDEV="$(echo ${i} | cut -d '=' -f 2)"
81 fi
82 ;;
83 cryptroot=*)
84 if [ "${i:10:4}" == 'UUID' ]; then
85 # mount by uuid
86 local uuid=$(echo ${i} | cut -d '=' -f 3)
87 CRYPTROOT="$(blkid -U ${uuid})"
88 elif [ "${i:10:5}" == 'LABEL' ]; then
89 # mount by label
90 local label=$(echo ${i} | cut -d '=' -f 3)
91 CRYPTROOT="$(blkid -L ${label})"
92 else
93 # mount by dev
94 CRYPTROOT="$(echo ${i} | cut -d '=' -f 2)"
95 fi
96 ;;
97 subvol=*)
98 MOUNTOPTS+=(-o subvol=${i#*=})
99 ;;
100 initdebug)
101 # Enable debug mode (this is gonna be slow)
102 DEBUG=1
103 ;;
104 interactive)
105 # Enable interactive mode
106 INTERACTIVE=1
107 ;;
108 quiet)
109 # Enable quiet boot (less output)
110 QUIET=1
111 ;;
112 esac
113 done
114 }
115
116
117 #
118 # Takes an encrypted device path and executes cryptsetup luksOpen. Returns path
119 # to the new decrypted block device. This path takes the rough form of
120 # /dev/mapper/_dev_sdx
121 #
122 setup_encrypted() {
123 local path="${1}"
124 local name="$(echo ${path} | tr / _)"
125
126 # Decrypted block dev path is /dev/mapper/${name}
127 cryptsetup luksOpen ${path} ${name}
128
129 # Notify user and wait for input on failure
130 if [ "$?" -gt 0 ]; then
131 # Ensure decryption was successful, it not, drop to debug shell and reboot once done.
132 printf 'ERROR: Could not decrypt device\n' >&2
133 printf 'Dropping to debug shell (will automatically reboot on exit).\n' >&2
134 /bin/bash --norc -i
135 printf 'Rebooting\n' >&2
136 printf 'b' > /proc/sysrq-trigger
137 fi
138
139 # Probe the new crypto device for an additional partition table
140 partprobe "/dev/mapper/${name}" 2>/dev/null 1>/dev/null
141
142 # Success. Return the path of the decrypted root device
143 echo "/dev/mapper/${name}"
144 }
145
146
147 #
148 # Waits up to a threshold (in seconds) for the specified path to appear as a
149 # block device. A check for device presence occurs once per second.
150 #
151 # If the device appears within the threshold time, the function returns early
152 # with error code 0 (all is well - no need to wait more).
153 #
154 # If the block device fails to appear within the threshold time, the function
155 # returns code 2 (not found)
156 #
157 # @param dev Path to the device to wait for
158 # @param timeout Maximum time (in seconds) to wait for the device to appear
159 #
160 wait_dev() {
161 local dev=${1}
162 local timeout=${2}
163
164 local wait=0
165
166 while [ ${wait} -lt ${timeout} ]; do
167 # If the path exists as a block device, exit the wait loop
168 [ -b "${dev}" ] && return 0
169 # Increment the wait counter
170 wait=$(( wait + 1 ))
171 # Wait another second for the device to appear
172 sleep 1
173 done
174
175 return 2
176 }
177
178
179 #
180 # Main function to keep the main operations code nicely separated from the
181 # rest.
182 #
183 main() {
184 # Mount the /proc and /sys filesystems.
185 mount -t tmpfs none /run
186 mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
187 mount -t proc -o nodev,noexec,nosuid proc /proc
188 mount -t devtmpfs none /dev
189 mount -t devpts devpts /dev/pts
190 mount -t tmpfs shm /dev/shm
191
192 local fakeroot='/mnt/root'
193
194 if [ ! -d "${fakeroot}" ]; then
195 log INFO "Fake root location ${fakeroot} does not exist. Creating."
196 mkdir ${fakeroot}
197 fi
198
199 parse_cmdline "$(cat /proc/cmdline)"
200
201 # display fanciful boot image
202 screen_init
203
204 log INFO "Root device: ${ROOTDEV}"
205
206 log INFO "Waiting up to 10 seconds for root device to appear."
207 wait_dev "${ROOTDEV}" 10
208
209 # Drop to maintenance shell if root device does not exist.
210 if [ "$?" -ne 0 ]; then
211 log ERROR "ERROR: Root device ${ROOTDEV} does not exist or can not be accessed."
212 log ERROR " Dropping to maintenance shell for troubleshooting."
213 log ERROR " (You may just need to wait longer for the root device)"
214 /bin/bash -i
215 fi
216
217 # Modprobe atkbd to ensure user can type password
218 for i in dm-crypt i8042 atkbd; do
219 printf 'Loading required module [%s]\n' "${i}"
220 modprobe "${i}" || printf 'Failed loading %s\n' "${i}"
221 done
222
223 if cryptsetup isLuks "${ROOTDEV}"; then
224 # Set new rootdev location (/dev/mapper/something). This will update it to
225 # the decrypted block device path.
226 log INFO "Root device ${ROOTDEV} is encrypted."
227 ROOTDEV=$(setup_encrypted ${ROOTDEV})
228 log INFO "New rootdev: ${ROOTDEV}"
229 fi
230
231 if [ ! -z "${CRYPTROOT}" ]; then
232 log INFO "Cryptroot defined. Changing rootdev to '${CRYPTROOT}'"
233 ROOTDEV="${CRYPTROOT}"
234 fi
235
236 # Drop to interactive shell if requested
237 if [ "${INTERACTIVE}" == 1 ]; then
238 log INFO "Interractive shell requested. Type 'exit' to continue boot sequence."
239 /bin/bash --norc
240 fi
241
242 # Mount the fakeroot.
243 log INFO "Mounting fakeroot"
244 mount_fakeroot ${ROOTDEV} ${fakeroot} ${MOUNTOPTS[@]}
245
246 # Ensure switch_root will be possible for destination fakeroot
247 if [ ! -f "${fakeroot}/sbin/init" ]; then
248 log ERROR "ERROR: Destination fakeroot ${fakeroot} does not have an init script."
249 log ERROR " Cannot proceed. Dropping to shell for troubleshooting."
250 /bin/bash -i
251 fi
252
253 # Boot the real McCoy
254 log INFO "Switching root"
255 exec switch_root ${fakeroot} /sbin/init
256
257 # This will be reached if previous command failed
258 log ERROR "There was an error performing switch_root to ${fakeroot}."
259 log ERROR "Starting recovery shell."
260 /bin/bash -i
261 }
262
263 main ${@}
|