-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathstick-install
executable file
·316 lines (255 loc) · 13 KB
/
stick-install
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
#!/bin/bash
# For installing a live ISO onto a flash drive or into an image file.
# This creates the special partition layout for data persistence.
. "$(dirname $(realpath "${0}"))/functions.sh"
. "$(dirname $(realpath "${0}"))/functions.bash"
cd_repo_root
check_dependencies parted fatattr dosfstools f2fs-tools libcdio-utils dialog ccze eatmydata
check_help_wanted() {
# give some rudimentary help if requested
case "${1}" in
"-h")
;&
"--help")
print_info "usage is ${0} DEVICE [ISO_FILE [INSTALL_VARIANT [SIZE_MB_PARTITION_FAT32]]]"
exit
;;
esac
}
select_target_device() {
OPTIONS=()
shopt -s nullglob
for DEVICE in /dev/{sd?,vd?,loop*}
do
DEVNAME=${DEVICE#/dev/}
# skip devices with no size, like empty card readers
[ -e /sys/block/${DEVNAME}/size ] || continue
SIZE=$(($(< /sys/block/${DEVNAME}/size) * 512))
SIZESTR=$(numfmt --to=iec-i --suffix B ${SIZE})
[[ "${DEVICE}" =~ "loop" ]] && p="p" || p=""
# skip mounted devices
grep -Eqs "^${DEVICE}(${p}[0-9]*)*" /proc/mounts && continue
NUMPARTITIONS=$(grep -c "${DEVNAME}${p}[[:digit:]]\+$" /proc/partitions | cat)
[ ${SIZE} -eq 0 ] && continue
BACKING=/sys/block/${DEVNAME}/loop/backing_file
[ -e ${BACKING} ] && BACKING="$(<${BACKING}) " || BACKING=""
[[ "${BACKING}" =~ "^/run/live/" ]] && continue
grep -qs 1 /sys/block/${DEVNAME}/removable || [[ -d /sys/block/${DEVNAME}/loop ]] && \
OPTIONS+=(${DEVICE} "${SIZESTR} ${BACKING}(${NUMPARTITIONS} $(ngettext 'partition' 'partitions' ${NUMPARTITIONS}))")
done
[ -z ${OPTIONS} ] && die "Could not find any suitable block device to install to!"
TEXT="Please choose the target device for installation of the live system.\n(Only removable block and loop devices with non-zero size are listed)"
TITLE="SELECT LIVE SYSTEM TARGET DEVICE"
display_menu "${TITLE}" "${TEXT}" "${OPTIONS[@]}"
}
select_live_iso() {
OPTIONS=()
for ISO in $(command ls -Lt {/run/live/findiso,artifacts}/*.iso 2>/dev/null || true )
do
SIZESTR=$(numfmt --to=iec-i --suffix B $(stat --dereference --format='%s' "${ISO}"))
OPTIONS+=(${ISO} "${SIZESTR} $(date --date=@$(stat --dereference --format='%Y' "${ISO}") "+%F %_H:%M:%S")")
done
TEXT="Please choose the live system to be installed on ${DEVICE}"
TITLE="SELECT LIVE SYSTEM ISO"
display_menu "${TITLE}" "${TEXT}" "${OPTIONS[@]}"
}
select_install_variant() {
OPTIONS=()
for VAR in $(command ls -Ltd variants.install/*/ | sed 's|/$||')
do
NUMHOOKS=$(find "${VAR}"/features/*/install-hooks/* -printf x | wc -c)
NUMFILES=$(find "${VAR}"/features/*/install-data/* -printf x | wc -c)
OPTIONS+=(${VAR#variants.install/} "(${NUMFILES} files, ${NUMHOOKS} hooks)")
done
TEXT="Please choose the install variant (determines final partition layout, boot loader configuration and ancillary content)."
TITLE="SELECT STICK INSTALL VARIANT"
display_menu "${TITLE}" "${TEXT}" "${OPTIONS[@]}"
}
define_first_partition_size() {
TEXT="Please define a size for the first partition, which is FAT32 and used for booting as well as for data exchange. The PortableApps.com packages might also be put here.\nFor the boot files to fit, specify \Zr\Zbat least 100MiB.\Zn\n\n"
TEXT+=" ${SIZE_MB_STICK} MiB storage device"
TEXT+="- ${SIZE_MB_LIVE_SYSTEM} MiB Live System ISO\n"
TEXT+="= $(( SIZE_MB_STICK - SIZE_MB_LIVE_SYSTEM )) MiB space for 1st (boot) & 3rd (persistence) partition\n"
TITLE="DEFINE FIRST PARTITION SIZE"
INIT=${1}
display_inputbox "${TITLE}" "${TEXT}" ${INIT}
}
# THANKS https://stackoverflow.com/questions/33085008/bash-round-to-nearest-multiple-of-4
round_int_to_next_multiple_of_16() {
echo $(( (${1} + 15 ) / 16 * 16 ))
}
is_f2fs_mountable() {
grep -qs f2fs /proc/filesystems && return 0
lsmod|grep -qs f2fs && return 0
modprobe -v f2fs 2>/dev/null || return 1
}
determine_install_fundamentals() {
# target DEVICE can be given as first parameter or interactively selected
[ -b "${1}" ] && DEVICE="${1}" || DEVICE=$(select_target_device)
# no device no burn
[ -z ${DEVICE} ] && die "no DEVICE chosen, cannot continue"
# loop devices have a different naming scheme (loop3p1 vs sdc1)
[[ "${DEVICE}" =~ "loop" ]] && p="p" || p=""
# proceed only if selected device is not mounted
grep -qs "^${DEVICE}${p}[[:digit:]]* " /proc/mounts && lsblk ${DEVICE} && die "partition(s) on ${DEVICE} currently mounted, not continuing!"
# the LIVE_IMAGE iso file can be given as second parameter or interactively selected
{ [ -f "artifacts/${2}" ] && LIVE_IMAGE="artifacts/${2}"; } || { [ -f "${2}" ] && LIVE_IMAGE="${2}"; } || LIVE_IMAGE="$(select_live_iso)"
# no ISO no play
[ -z ${LIVE_IMAGE} ] && die "no LIVE_IMAGE chosen, cannot continue"
# The "install variant" defines the set of install procedures to be executed
[ -n "${3}" ] && [ -d "variants.install/${3}" ] && INSTALL_VARIANT="${3}" || INSTALL_VARIANT=$(select_install_variant)
# no install procedures no mission
[ -z ${INSTALL_VARIANT} ] && die "no INSTALL_VARIANT chosen, cannot continue"
# make endpoint variables available to sub-processes
export DEVICE LIVE_IMAGE INSTALL_VARIANT
print_info "Live system image: ${LIVE_IMAGE}"
print_info "Install variant: ${INSTALL_VARIANT}"
print_info "Target Device: ${DEVICE}"
# any partitions on the device? => show the layout
[ $(grep -c "${DEVICE#/dev/}[[:alnum:]]\+$" /proc/partitions) -eq 0 ] \
|| { print_warn "overwriting existing partition layout:"; parted --script ${DEVICE} unit MiB print free; }
}
determine_partition_sizes() {
# get block device size in MebiByte
SIZE_STICK=$(blockdev --getsize64 ${DEVICE})
SIZE_MB_STICK=$(( SIZE_STICK / (1024 * 1024) ))
# get ISO live image size in MebiByte
SIZE_LIVE_SYSTEM=$(stat --dereference --format=%s ${LIVE_IMAGE})
SIZE_MB_LIVE_SYSTEM=$(( SIZE_LIVE_SYSTEM / (1024 * 1024) ))
print_info "figuring out partition sizes for $(numfmt --to=iec-i --suffix B ${SIZE_STICK}) storage device ${DEVICE}"
[ -n "${4}" ] && SIZE_MB_PARTITION_FAT32=${4} || \
SIZE_MB_PARTITION_FAT32=$(define_first_partition_size 1500)
# no size no partitioning
[ ! ${SIZE_MB_PARTITION_FAT32:-0} -gt 0 ] && die "No partition size set, cannot continue"
# try to put partition boundaries on flash cell erasure block boundaries assumed to be 16MiB
SIZE_MB_PARTITION_FAT32=$(round_int_to_next_multiple_of_16 ${SIZE_MB_PARTITION_FAT32})
# partition has 32768s = 16MiB offset
END_MB_PARTITION_FAT32=$((16 + ${SIZE_MB_PARTITION_FAT32}))
# >~ 114 MiB for the filesystem gods
SIZE_MB_SPACE_BUFFER=150
SIZE_MB_PARTITION_LIVE_IMAGE=$(( SIZE_MB_LIVE_SYSTEM + SIZE_MB_SPACE_BUFFER ))
END_MB_PARTITION_LIVE_IMAGE=$(round_int_to_next_multiple_of_16 $((END_MB_PARTITION_FAT32 + SIZE_MB_PARTITION_LIVE_IMAGE)) )
}
create_partitions() {
print_info "creating (traditional) DOS partition table.."
parted --script ${DEVICE} mklabel msdos
# create an EFI boot & data exchange partition at an offset of 16MiB (to align with page size)
parted --script --align=optimal ${DEVICE} mkpart primary fat32 32768s ${END_MB_PARTITION_FAT32}MiB
parted ${DEVICE} set 1 boot on
print_info "created 16MiB - ${END_MB_PARTITION_FAT32}MiB fat32 data exchange / EFI boot partition"
# create the main live-system partition
parted --script --align=optimal -- ${DEVICE} mkpart primary ${END_MB_PARTITION_FAT32}MiB ${END_MB_PARTITION_LIVE_IMAGE}MiB
print_info "created ${END_MB_PARTITION_FAT32}MiB - ${END_MB_PARTITION_LIVE_IMAGE}MiB live system partition (will be formatted ext2)"
# create a small persistence partition that will be grown on first boot to device limits
# allows distributing a smaller image file that will fit USB sticks of varying size
SIZE_MB_PARTITION_PERSISTENCE=256
END_MB_PARTITION_PERSISTENCE=$(( END_MB_PARTITION_LIVE_IMAGE + SIZE_MB_PARTITION_PERSISTENCE ))
parted --script --align=optimal -- ${DEVICE} mkpart primary ${END_MB_PARTITION_LIVE_IMAGE}MiB ${END_MB_PARTITION_PERSISTENCE}MiB
print_info "created ${END_MB_PARTITION_LIVE_IMAGE}MiB - ${END_MB_PARTITION_PERSISTENCE}MiB persistence partition (will be formatted f2fs)"
print_info "rereading partition table and checking partition alignment"
partprobe --summary ${DEVICE}
parted ${DEVICE} unit MiB print free
parted ${DEVICE} align-check minimal 2
parted ${DEVICE} align-check optimal 2
parted ${DEVICE} align-check minimal 3
parted ${DEVICE} align-check optimal 3
print_info "setting 2nd + 3rd partition to type 0 / empty because.. Windows 10 will propose to format it otherwise"
eatmydata sfdisk --part-type ${DEVICE} 2 0
eatmydata sfdisk --part-type ${DEVICE} 3 0
}
create_filesystems() {
print_info "creating filesystems"
MAIN_LABEL=live-system
FAT_LABEL=$(basename "${INSTALL_VARIANT%.*}")
# the EFI boot partition needs to be FAT32 which
# is perfect for file exchange with inferior OSs
mkfs.vfat -vn ${FAT_LABEL} -F 32 ${DEVICE}${p}1
print_info "EFI boot partition formatted"
# the live system main storage partition
mkfs.ext4 -FL ${MAIN_LABEL} -O ^has_journal -m 0 -i 1048576 ${DEVICE}${p}2
# -F: Force create filesystem (unless mounted)
# -L: set volume label (maximum length 16 bytes)
# -O: create without journal
# -m: percentage of blocks reserved for the super-user
# -i: larger bytes-per-inode ratio > fewer inodes
print_info "live system partition formatted"
# persistence storage
mkfs.f2fs -fd 5 -l live-memory -O extra_attr,inode_checksum,sb_checksum,compression,encrypt ${DEVICE}${p}3
# extra_attr: Enable extra attr feature, required for some of the other features.
# inode_checksum: Enable inode checksum. Requires extra attr.
# sb_checksum: Enable superblock checksum.
# compression: Enable support for filesystem level compression. Requires extra attr.
# encrypt: Enable support for filesystem level encryption.
print_info "persistence partition formatted"
}
# CLEAN-UP TRAPS
# once set, these will fire on script exit or abortion
trap_remove_mountdir() { rmdir ${MOUNTDIR}; }
trap_remove_mountsubdirs() { rmdir ${MOUNTDIR}/*; }
trap_umount_partitions() {
printf '\n'
print_info "unmounting partitions"
umount -v ${DEVICE}${p}1
umount -v ${DEVICE}${p}2
! is_f2fs_mountable || umount -v ${DEVICE}${p}3
print_info "Installation to ${DEVICE} took $(format_timespan $(($(date +%s) - ${STARTTIME})))"
print_success "It is now safe to remove device ${DEVICE}.\n" \
"\tIf no errors have been reported above, the live stick should work fine.\n" \
"\tPlease report any problems you find, and good luck!"
}
mount_filesystems() {
# create a temporary directory to hold the mounts
MOUNTDIR=$(mktemp --tmpdir --directory stick-install.XXXX)
# on script termination: at this point, only the temporary mountdir to clean up
trap "trap_remove_mountdir" EXIT SIGHUP SIGQUIT SIGTERM
EFIBOOT=${MOUNTDIR}/efiboot
ISOSTORE=${MOUNTDIR}/isostore
PERSISTENCESTORE=${MOUNTDIR}/persistencestore
# create mount directories
mkdir -pv ${EFIBOOT} ${ISOSTORE} ${PERSISTENCESTORE}
# on script termination: also remove the created sub directories
trap "trap_remove_mountsubdirs; trap_remove_mountdir" EXIT SIGHUP SIGQUIT SIGTERM
print_info "mounting partitions"
mount -v ${DEVICE}${p}1 ${EFIBOOT}
mount -v ${DEVICE}${p}2 ${ISOSTORE}
is_f2fs_mountable && mount -v ${DEVICE}${p}3 ${PERSISTENCESTORE}
# on script termination: add unmounting storage as first step to the clean-up trap queue
trap "trap_umount_partitions; trap_remove_mountsubdirs; trap_remove_mountdir" EXIT SIGHUP SIGQUIT SIGTERM
}
execute_install_hooks() {
# work in a subshell so the directory change is ineffective outside
(
cd build
mkdir -p install
export INSTALL_TMPDIR=$(mktemp --directory --tmpdir=install)
(
cd ${INSTALL_TMPDIR}
# and copy out install features anew
transmogrify_features "../../../variants.install/${INSTALL_VARIANT}"
for INSTALL_HOOK in install/hooks/*
do
(
printf '\n'
print_info "Invoking install hook ${INSTALL_HOOK##*/}"
source "${INSTALL_HOOK}"
) &
# set -e & subshell? https://unix.stackexchange.com/a/254676/18150
wait $! || die "Problem occurred in ${INSTALL_HOOK}"
done
)
)
}
install_main() {
is_root_user_or_die
check_help_wanted "{@}"
determine_install_fundamentals "${@}"
determine_partition_sizes "${@}"
STARTTIME=$(date +%s)
create_partitions
create_filesystems
mount_filesystems
execute_install_hooks
# unmounts done by traps
}
install_main "${@}"
# vim:ts=4:sts=4:sw=4:expandtab