-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcreate
executable file
·397 lines (344 loc) · 14.2 KB
/
create
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
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
#!/usr/bin/env bash
# Copyright (C) 2017, 2018, 2019, 2020, 2022 Marius Bakke <marius@gnu.org>
# Copyright (C) 2022 David Larsson <david.larsson@selfhosted.xyz>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# cancel script on error
set -e
shopt -s globstar
. ./common.sh
export LANG=en_US.utf8
# This is the root filesystem mount-point for guix.
TARGET_ROOT=$(mktemp -d /tmp/mnt_XXXXXX)
CLEANUP+=("rmdir $TARGET_ROOT")
if [[ -n "$OS_VARIANT" ]]; then
if [[ ! -d "$VARIANTS_DIR" ]]; then
log "OS Variants directory $VARIANTS_DIR doesn't exist"
exit 1
fi
VARIANT_CONFIG="$VARIANTS_DIR/$OS_VARIANT.scm"
if [[ ! -e "$VARIANT_CONFIG" ]]; then
log "Configuration file for $OS_VARIANT does not exist"
exit 1
fi
fi
if [[ $DISK_COUNT -gt 0 ]] && [[ -n "$DISK_0_PATH" ]]; then
TARGET_DEVICE=$DISK_0_PATH
else
log "At least one disk is needed."
exit 1
fi
guix_setup(){
# Prepare the time machine if relevant.
if [ -n "$OSP_COMMIT" ] || [ -n "$OSP_BRANCH" ] || [ -n "$OSP_REPO_URI" ]; then
GUIX="$GUIX time-machine"
if [ -n "$OSP_REPO_URI" ]; then
GUIX="$GUIX --url=$OSP_REPO_URI"
fi
if [ -n "$OSP_COMMIT" ]; then
GUIX="$GUIX --commit=$OSP_COMMIT"
fi
if [ -n "$OSP_BRANCH" ]; then
GUIX="$GUIX --branch=$OSP_BRANCH"
fi
if [ -n "$OSP_DISABLE_AUTHENTICATION" ]; then
GUIX="$GUIX --disable-authentication"
fi
log "Invoking time machine with the following parameters:"
log " $(echo $GUIX | cut --complement -f1,2 -d' ')"
GUIX="$GUIX -- "
fi
GUIX="$GUIX system"
if [ -n "$OSP_SYSTEM" ]; then
GUIX="$GUIX --system=$OSP_SYSTEM"
fi
if [ -n "$OSP_TARGET" ]; then
GUIX="$GUIX --target=$OSP_TARGET"
fi
echo "$GUIX"
}
# $1 like: /dev/mapper/vg--drbd-e7deba31--7f30--4475--9ff3--2565f232f780.disk0
partition_disk(){
local device="$1"
local size="$2"
# Ensure the disk we partition is free from all previous "magic"
# strings, such as filesystem and partition table signatures. For
# example, in case any partition contains an LVM2_member
# signature, we need to remove it.
"${WIPEFS}" --all --force "${device}"
# It has happened that some VM has kernel panicked due to first
# sectors not being properly wiped, and writing some random stuff
# there has solved it:
head -c 3145728 /dev/urandom > "$device"; sync
# Create bios_grub partition and offset so it's easy to use either
# of MBR or GPT or to switch between them.
# https://www.gnu.org/software/grub/manual/grub/html_node/BIOS-installation.html#BIOS-installation
# If part2 should have a size limit, then use that for the second partition.
log Creating 2 partitions on "$device"
partition_device "$device" "$size"
}
# $1 like: /dev/mapper/vg--drbd-e7deba31--7f30--4475--9ff3--2565f232f780.disk0
# find_partition_path returns the second partition's filepath
find_partition_path(){
local device="$1"
local part="$2"
local mappedname
# This is somewhat tricky. kpartx partitions always end up in /etc/mapper,
# but TARGET_DEVICE is typically one of:
# * /dev/loopN (for files)
# * /dev/drbdN
# * /dev/vg/a-b-c-d.disk0
# For the first two, the mapped device becomes e.g. /dev/mapper/drbdNpN.
# For the latter, the mapped device becomes /dev/mapper/vg-a--b--c--d.disk0pN.
if [[ "$device" =~ "/dev/loop" ]]; then
echo "${device}p${part}"
else
mappedname="$("$KPARTX" -l "$device" | awk '{ print $1 }' | grep -m 1 "p${part}$")"
echo "/dev/mapper/${mappedname}"
fi
}
# $1 like: /dev/mapper/vg--drbd-e7deba31--7f30--4475--9ff3--2565f232f780.disk0
create_filesystems(){
local TARGET_DEVICE="$1"
local TARGET_PARTITION="$2"
local FS_TYPE="$3"
local LAYOUT="$4"
local SWAP_SPACE=4G
local TARGET_DEV_PART2
TARGET_DEV_PART2="${TARGET_PARTITION}"
if [[ "$LAYOUT" == basic ]]; then
log "Formatting $TARGET_DEV_PART2 with $FS_TYPE"
format_device "$TARGET_DEV_PART2" "$FS_TYPE"
elif [[ "$FS_TYPE" == btrfs ]]; then
command -v "${BTRFS}" || {
log "btrfs requested but not installed"
exit 1
}
log "Creating btrfs subvolume layout on $TARGET_DEV_PART2"
format_device "$TARGET_DEV_PART2" "$FS_TYPE"
# For btrfs, create a "system-root" subvolume that holds the
# root file system.
local BTRFS_ROOT="${TARGET_ROOT}/system-root"
# Mount and create root filesystem
"$MOUNT" "$TARGET_DEV_PART2" "$TARGET_ROOT" || {
log Failed to "$MOUNT" "$TARGET_DEV_PART2" onto "$TARGET_ROOT"
return 1
}
"${BTRFS}" subvolume create "$BTRFS_ROOT"
# Setup the SWAP subvolume
"${BTRFS}" subvolume create "$BTRFS_ROOT/swap"
# Setup the SWAP-file
chmod 700 "$BTRFS_ROOT/swap"
truncate -s 0 "$BTRFS_ROOT/swap/swapfile"
"${CHATTR}" +C "$BTRFS_ROOT/swap/swapfile"
"${BTRFS}" property set "$BTRFS_ROOT/swap" compression none
"${FALLOCATE}" -l "$SWAP_SPACE" "$BTRFS_ROOT/swap/swapfile"
chmod 600 "$BTRFS_ROOT/swap/swapfile"
"${MKSWAP}" -f "$BTRFS_ROOT/swap/swapfile"
# Setup additional subvolumes
mkdir -p "$BTRFS_ROOT/gnu"
mkdir -p "$BTRFS_ROOT/var"
"${BTRFS}" subvolume create "$BTRFS_ROOT/gnu/store"
"${BTRFS}" subvolume create "$BTRFS_ROOT/var/lib"
"${BTRFS}" subvolume create "$BTRFS_ROOT/var/log"
"${BTRFS}" subvolume create "$BTRFS_ROOT/home"
"$UMOUNT" "$TARGET_ROOT"
elif [[ "$LAYOUT" == "advanced" ]]; then
# Assume LVM.
command -v "${PVCREATE}" || {
log "LVM requested but lvm2 is not available"
exit 1
}
"${PARTED}" --script "$TARGET_DEVICE" \
set 2 lvm on
"${PVCREATE}" -f "$TARGET_DEV_PART2"
"${VGCREATE}" "$INSTANCE_NAME"_vg01 "$TARGET_DEV_PART2"
CLEANUP+=("${VGCHANGE} -an ${INSTANCE_NAME}_vg01")
ROOT_SIZE='-l 10%VG'
HOME_SIZE='-l 10%VG'
GNU_STORE_SIZE='-l 45%VG'
VAR_LOG_SIZE='-l 10%VG'
VAR_LIB_SIZE='-l 20%VG'
SWAP_SIZE='-l 100%FREE'
"${LVCREATE}" --yes -n lv_root $ROOT_SIZE -W y "$INSTANCE_NAME"_vg01
"${LVCREATE}" --yes -n lv_home $HOME_SIZE -W y "$INSTANCE_NAME"_vg01
"${LVCREATE}" --yes -n lv_gnu_store $GNU_STORE_SIZE -W y "$INSTANCE_NAME"_vg01
"${LVCREATE}" --yes -n lv_var_log $VAR_LOG_SIZE -W y "$INSTANCE_NAME"_vg01
"${LVCREATE}" --yes -n lv_var_lib $VAR_LIB_SIZE -W y "$INSTANCE_NAME"_vg01
"${LVCREATE}" --yes -n lv_swap $SWAP_SIZE -W y "$INSTANCE_NAME"_vg01
# -f (force) is needed if reinstalling and previous
# logical-volume also was a swap volume and still has that
# signature, else mkswap command will wait for user
# confirmation
"${MKSWAP}" -f --label "$INSTANCE_NAME"-swap \
/dev/"$INSTANCE_NAME"_vg01/lv_swap
for lv in lv_root lv_home lv_gnu_store lv_var_log lv_var_lib; do
format_device "/dev/${INSTANCE_NAME}_vg01/${lv}" "$FS_TYPE"
done
else
log "Unsupported filesystem type and/or layout combination:"
log " FS_TYPE: $FS_TYPE LAYOUT: $LAYOUT"
return 1
fi
}
prep_init_mount_point(){
local TARGET_DEVICE="$1"
local TARGET_PARTITION="$2"
local FS_TYPE="$3"
local LAYOUT="$4"
local TARGET_DEV_PART2="$TARGET_PARTITION"
if [[ "$LAYOUT" == "basic" ]]; then
# Basic layouts can just be mounted directly.
"$MOUNT" "$TARGET_DEV_PART2" "$TARGET_ROOT"
CLEANUP+=("$UMOUNT $TARGET_ROOT")
else
if [[ "$FS_TYPE" == "btrfs" ]]; then
# The advanced btrfs layout uses a system-root subvolume.
"$MOUNT" -o "subvol=system-root" "$TARGET_DEV_PART2" "$TARGET_ROOT"
CLEANUP+=("$UMOUNT $TARGET_ROOT")
else
# The advanced layout for non-btrfs filesystems is setup with LVM
log Mounting LVM logical volumes from "${INSTANCE_NAME}"_vg01 \
of "$TARGET_DEV_PART2"
"$MOUNT" "/dev/${INSTANCE_NAME}_vg01/lv_root" "$TARGET_ROOT"
CLEANUP+=("$UMOUNT $TARGET_ROOT")
for lv in lv_home lv_gnu_store lv_var_log lv_swap lv_var_lib; do
lv="${lv#lv_}"
mkdir -p "$TARGET_ROOT"/"${lv//_/\/}" >&2
if [[ ! "$lv" == swap ]]; then
log running: "$MOUNT" "/dev/${INSTANCE_NAME}_vg01/lv_${lv}" \
"$TARGET_ROOT"/"${lv//_/\/}" >&2
"$MOUNT" "/dev/${INSTANCE_NAME}_vg01/lv_${lv}" \
"$TARGET_ROOT"/"${lv//_/\/}"
CLEANUP+=("$UMOUNT $TARGET_ROOT/${lv//_/\/}")
fi
done
fi
fi
}
# guix system init on a target device's second partition
initialize_guix(){
local guix_command="$1"
local config="$2"
local mount_point="$3"
export TARGET_DEVICE="$4"
export TARGET_UUID="$5"
export FS_TYPE="$6"
# Build the system configuration and save GC root.
local GC_ROOT="$GCROOTSDIR/$INSTANCE_NAME"
rm -f "$GC_ROOT"
$guix_command build --fallback --no-grafts -r "$GC_ROOT" "$config"
# Install GuixSD
$guix_command init "$config" "$mount_point" || {
log Failed to guix system init "$config" "$mount_point"
return 1
}
}
main(){
local DEFAULT_VARIANT_CONFIG="${EXAMPLEDIR}/dynamic.scm"
local VARIANT_CONFIG="${VARIANT_CONFIG:-$DEFAULT_VARIANT_CONFIG}"
log Running with options set to:
log INSTANCE_NAME: "${INSTANCE_NAME}"
log TARGET_DEVICE: "${TARGET_DEVICE}"
log DISK_0_SIZE: "${DISK_0_SIZE}"
log OSP_FILESYSTEM: "${OSP_FILESYSTEM:-ext4}"
log OSP_LAYOUT: "${OSP_LAYOUT:-basic}"
log VARIANT_CONFIG: "${VARIANT_CONFIG}"
log OSP_HURD: "${OSP_HURD}"
# Make Guix use the specified CACHE_DIR to store Guile and Git caches.
if [[ -n "$CACHE_DIR" ]] && [[ ! -d "$CACHE_DIR" ]]; then
mkdir -p "$CACHE_DIR"
fi
if [[ -n "$GCROOTSDIR" ]] && [[ ! -d "$GCROOTSDIR" ]]; then
mkdir -p "$GCROOTSDIR"
fi
if [[ -n "$CACHE_DIR" ]]; then
export XDG_CACHE_HOME="$CACHE_DIR"
fi
# Check if TARGET_DEVICE is a real block device, and losetup it if
# it isn't (for example when using a file disk):
if [[ ! -b "$TARGET_DEVICE" ]]; then
TARGET_DEVICE=$(losetup_device "$TARGET_DEVICE")
CLEANUP+=("${LOSETUP} -d $TARGET_DEVICE")
fi
# Check OS parameters and set an appropriate $GUIX command that
# will use guix time-machine if needed.
local GUIX_COMMAND
GUIX_COMMAND=$(guix_setup)
log GUIX_COMMAND is: "$GUIX_COMMAND"
# Say 'cheeese'.
$GUIX_COMMAND --version
if [[ "${OSP_HURD}" = "true" ]]; then
log "Creating GNU/Hurd image..."
# Reserve 50 MiB to ensure the image does not exceed Ganeti size.
IMAGE_SIZE=$((DISK_0_SIZE - 50))
DISK_IMAGE=$($GUIX_COMMAND image -t hurd-qcow2 --image-size=${IMAGE_SIZE}M "${VARIANT_CONFIG}")
if [[ -b "${TARGET_DEVICE}" ]]; then
$QEMU_IMG dd bs=4M -O raw if="$DISK_IMAGE" of="$TARGET_DEVICE"
else
$QEMU_IMG convert -O raw "$DISK_IMAGE" "$TARGET_DEVICE"
fi
# Nothing left to do.
return
fi
# A basic 2 partitions setup.
partition_disk "$TARGET_DEVICE" "$PART_SIZE"
log DONE PARTITIONING.
# Add partition mappings. Test $DISK_0_PATH because $TARGET_DEVICE
# may get reassigned by losetup above (XXX).
if [[ -b "$DISK_0_PATH" ]]; then
"${KPARTX}" -a "$TARGET_DEVICE"
CLEANUP+=("${KPARTX} -d $TARGET_DEVICE")
fi
# Each filesystem and layout option combination is handled in the
# create_filesystems function. This will possibly setup lvm based
# on the OSP options.
local TARGET_PARTITION
TARGET_PARTITION=$(find_partition_path "$TARGET_DEVICE" 2)
log TARGET_PARTITION set to "$TARGET_PARTITION"
if [[ -n "$OSP_LUKS_PASSPHRASE" ]]; then
local luks_name="${TARGET_PARTITION##*/}_mapped"
local luks_mapped="/dev/mapper/${luks_name}"
log "LUKS passphrase is set. Starting LUKS formatting."
luks_format "$TARGET_PARTITION" "$OSP_LUKS_PASSPHRASE"
log Mapping LUKS device "$TARGET_PARTITION" to "$luks_mapped"
luks_open "$TARGET_PARTITION" "$luks_name" "$OSP_LUKS_PASSPHRASE"
CLEANUP+=("luks_close $luks_name")
# This variable is used by Guix configurations.
LUKS_UUID="$(find_uuid ${TARGET_PARTITION})"
log LUKS_UUID is $LUKS_UUID
export LUKS_UUID
# Hijack $TARGET_PARTITION to the mapped device.
TARGET_PARTITION="$luks_mapped"
log TARGET_PARTITION is now "$TARGET_PARTITION"
fi
log Creating file system on "$TARGET_PARTITION"
create_filesystems "$TARGET_DEVICE" "$TARGET_PARTITION" "${OSP_FILESYSTEM:-ext4}" "${OSP_LAYOUT:-basic}"
log DONE CREATING FILESYSTEMS
# Prep the mount point to initialize guix
prep_init_mount_point "$TARGET_DEVICE" "$TARGET_PARTITION" "${OSP_FILESYSTEM:-ext4}" "${OSP_LAYOUT:-basic}"
TARGET_UUID="$(find_uuid ${TARGET_PARTITION})"
log TARGET_UUID="$TARGET_UUID"
# Initialize/install Guix!
log Guix will now be initialized on "$TARGET_ROOT"
# Since TARGET_PARTITION will generally refer to
# /dev/mapper/loop0p2_mapped if we are using luks, we need to find
# the UUID of just the second partition of the TARGET_DEVICE
initialize_guix "$GUIX_COMMAND" "$VARIANT_CONFIG" "$TARGET_ROOT" "$TARGET_DEVICE" "$TARGET_UUID" "${OSP_FILESYSTEM:-ext4}"
}
[[ "$1" == '--source-only' ]] || main "$@"
# Execute cleanups.
cleanup
trap - EXIT
exit 0