File: setup_nested_hybrid_system.sh

package info (click to toggle)
snapd 2.72-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 80,412 kB
  • sloc: sh: 16,506; ansic: 16,211; python: 11,213; makefile: 1,919; exp: 190; awk: 58; xml: 22
file content (393 lines) | stat: -rwxr-xr-x 13,370 bytes parent folder | download
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
#!/bin/bash

set -uxe

run_muinstaller() {
    # shellcheck source=tests/lib/prepare.sh
    . "${TESTSLIB}/prepare.sh"

    # shellcheck source=tests/lib/nested.sh
    . "${TESTSLIB}/nested.sh"

    local model_assertion="${1}"
    # since store_dir is optional, we need to make sure it's set to an empty
    # string to prevent using an unbound variable
    local store_dir="${2:-}"
    local gadget_snap="${3}"
    local gadget_assertion="${4}"
    local kernel_snap="${5}"
    local kernel_assertion="${6}"
    local label="${7}"
    local disk="${8}"
    local kern_mods_comp="${9}"
    local passphrase="${10}"
    local recovery_key_out="${11}"
    local exit_at_preinstall="${12}"
    shift 12
    local extra_muinstaller_args=("${@}")

    # ack the needed assertions
    snap ack "${kernel_assertion}"
    snap ack "${gadget_assertion}"

    local key_name
    key_name=$(nested_get_snakeoil_key)
    local snakeoil_key="${PWD}/${key_name}.key"
    local snakeoil_cert="${PWD}/${key_name}.pem"

    # unpack the gadget snap
    unsquashfs -d pc-gadget "${gadget_snap}"

    # sign the shim binary
    tests.nested secboot-sign gadget pc-gadget "${snakeoil_key}" "${snakeoil_cert}"
    snap pack --filename=pc.snap pc-gadget/

    build_snapd_snap .
    mv snapd_*.snap snapd.snap

    # prepare a classic seed
    kmodsarg=""
    if [ -n "$kern_mods_comp" ]
    then kmodsarg="--comp $kern_mods_comp"
    fi
    # shellcheck disable=SC2086
    snap prepare-image --classic \
        --channel=edge \
        --snap "${kernel_snap}" \
        $kmodsarg \
        --snap pc.snap \
        --snap snapd.snap \
        "${model_assertion}" \
        ./classic-seed

    mv ./classic-seed/system-seed/systems/* "./classic-seed/system-seed/systems/${label}"

    if [ -n "${store_dir}" ]; then
        # if we have a store setup, then we should take it down for now
        "${TESTSTOOLS}/store-state" teardown-fake-store "${store_dir}"
    fi

    # build the muinstaller snap
    if [ -z "$(command -v snapcraft)" ]; then
        snap install snapcraft --candidate --classic
    fi
    "${TESTSTOOLS}/lxd-state" prepare-snap
    (cd "${TESTSLIB}/muinstaller" && snapcraft)

    local muinstaller_snap
    muinstaller_snap="$(find "${TESTSLIB}/muinstaller/" -maxdepth 1 -name '*.snap')"

    # create a VM and mount a cloud image
    tests.nested build-image classic

    # TODO: nested classic images do not support secure boot today so
    #       this will not work to test the secure boot installer. So for
    #       now the workaround is to boot classic to create user/ssh
    #       keys, shutdown down, convert disk from qcow2->raw and rename
    #       from classic->core and use nested_start_core_vm (like below)
    #
    # start it so that cloud-init creates ssh keys and user
    # We set a serial for our disk to easily locate it when invoking muinstaller (virtio-target)
    NESTED_PARAM_EXTRA="-drive file=${disk},if=none,snapshot=off,format=raw,id=disk2 \
        -device virtio-blk-pci,drive=disk2,serial=target"
    tests.nested create-vm classic --extra-param "${NESTED_PARAM_EXTRA}"

    # make sure classic image is bootable with snakeoil keys
    # TODO: move to nested_create_classic_image
    # XXX: use assets from gadget instead?
    for s in BOOT/BOOTX64.EFI ubuntu/shimx64.efi; do
        remote.exec "sudo cp -a /boot/efi/EFI/${s} /tmp"
        remote.exec "sudo chmod 755 /tmp/$(basename ${s})"
        remote.pull /tmp/"$(basename ${s})" .
        nested_secboot_sign_file "$(basename ${s})" "${snakeoil_key}" "${snakeoil_cert}"
        remote.push "$(basename ${s})"
        remote.exec "sudo mv $(basename ${s}) /boot/efi/EFI/${s}"
    done

    remote.exec "sudo sh -c 'echo SNAPD_DEBUG=1 >> /etc/environment'"
    # push our snap down
    # TODO: this abuses /var/lib/snapd to store the deb so that mk-initramfs-classic
    # can pick it up. the real installer will also need a very recent snapd
    # in its on disk-image to support seeding
    remote.push "${GOHOME}"/snapd_*.deb
    remote.exec "sudo mv snapd_*.deb /var/lib/snapd/"
    remote.exec "sudo apt install -y /var/lib/snapd/snapd_*.deb"

    # push our seed down
    # TODO: merge with classic /var/lib/snapd/seed eventually
    # XXX: port scp -r to remote.push
    #remote.push ./classic-seed/system-seed/ '~/'
    sshpass -p ubuntu scp -r -P 8022 -o ConnectTimeout=10 \
        -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \
        ./classic-seed/system-seed/ user1@localhost:~/install-seed
    remote.exec "sudo mv /home/user1/install-seed /var/lib/snapd/"

    # shutdown the classic vm to install with a core VM that supports
    # secboot/tpm
    tests.nested vm stop
    sync

    # HACK: convert "classic" qcow2 to raw "core" image because we need
    # to boot with OVMF we really should fix this so that classic and
    # core VMs are more similar
    qemu-img convert -f qcow2 -O raw \
        "${NESTED_IMAGES_DIR}/$(nested_get_image_name classic)" \
        "${NESTED_IMAGES_DIR}/$(nested_get_image_name core)"
    # and we don't need the classic image anymore
    # TODO: uncomment
    # rm -f  "$NESTED_IMAGES_DIR/$(nested_get_image_name classic)"
    # TODO: this prevents "nested_prepare_ssh" inside nested_start_core_vm
    #       from running, we already have a user so this is not needed
    local image_name
    image_name="$(nested_get_image_name core)"
    touch "${NESTED_IMAGES_DIR}/${image_name}.configured"
    tests.nested create-vm core --extra-param "${NESTED_PARAM_EXTRA}"

    # bind mount new seed
    remote.exec "sudo mkdir -p /var/lib/snapd/seed"
    remote.exec "sudo mount -o bind /var/lib/snapd/install-seed /var/lib/snapd/seed"

    # when only interested in preinstall environment exit here
    if [ "$exit_at_preinstall" = "true" ]; then
        return 0
    fi

    # push and install muinstaller
    remote.push "${muinstaller_snap}"
    remote.exec "sudo snap install --classic --dangerous $(basename "${muinstaller_snap}")"

    # run installation
    local install_disk
    install_disk=$(remote.exec "readlink -f /dev/disk/by-id/virtio-target")

    if [ -n "${HYBRID_SYSTEM_MK_ROOT_FS-}" ]; then
        remote.push "${HYBRID_SYSTEM_MK_ROOT_FS}" /home/user1/custom-rootfs.sh
        remote.exec "chmod +x /home/user1/custom-rootfs.sh"
    fi
    remote.exec "tee /home/user1/mk-classic-rootfs-wrapper.sh" <<\EOF
#!/bin/bash
set -eu
/snap/muinstaller/current/bin/mk-classic-rootfs.sh "$@"
if [ -x /home/user1/custom-rootfs.sh ]; then
  /home/user1/custom-rootfs.sh "$@"
fi
EOF
    remote.exec "chmod +x /home/user1/mk-classic-rootfs-wrapper.sh"

    muinstaller_args=()
    muinstaller_args+=("-label" "$label")
    muinstaller_args+=("-device" "$install_disk")
    muinstaller_args+=("-rootfs-creator" "/home/user1/mk-classic-rootfs-wrapper.sh")
    if [ -n "$passphrase" ]; then
        muinstaller_args+=("-passphrase" "\"$passphrase\"")
    fi
    if [ -n "$recovery_key_out" ]; then
        muinstaller_args+=("-recovery-key-out" "/tmp/rkey.out")
    fi
    muinstaller_args+=("${extra_muinstaller_args[@]}")
    remote.exec sudo muinstaller "${muinstaller_args[@]}"

    if [ -n "$recovery_key_out" ]; then
        remote.pull "/tmp/rkey.out" "$recovery_key_out"
    fi

    remote.exec "sudo sync"

    # Stop and remove the classic vm now that the attached disk (${disk})
    # contains a just installed UC image.
    tests.nested vm remove
    sync

    # HACK: rename to "core" image because we need to boot with OVMF
    # we really should fix this so that classic and core VMs are more similar
    mv "${disk}" "${NESTED_IMAGES_DIR}/${image_name}"

    # Change seed part label to capitals so we cover that use case
    image_path="${NESTED_IMAGES_DIR}/${image_name}"
    kpartx -asv "$image_path"
    fatlabel /dev/disk/by-label/ubuntu-seed UBUNTU-SEED
    if ! kpartx -d "${image_path}"; then
        # Sometimes there are random failures, let's wait and re-try
        sleep 1
        kpartx -d "${image_path}"
    fi

    if [ -n "${store_dir}" ]; then
        # if we had a store setup, then we should bring it back up
        "${TESTSTOOLS}/store-state" setup-fake-store "${store_dir}"
    fi

    # Start installed image
    if [ -n "$passphrase" ]; then
        tests.nested create-vm core --keep-firmware-state --passphrase "$passphrase"
    else
        tests.nested create-vm core --keep-firmware-state
    fi
}

main() {
    local model_assertion=""
    local store_dir=""
    local gadget_snap=""
    local gadget_assertion=""
    local kernel_snap=""
    local kernel_assertion=""
    local label="classic"
    local disk=""
    local kern_mods_comp=""
    local passphrase=""
    local recovery_key_out=""
    local exit_at_preinstall=""
    local extra_muinstaller_args=()
    while [ $# -gt 0 ]; do
        case "$1" in
            --model)
                model_assertion="${2}"
                shift 2
                ;;
            --store-dir)
                store_dir="${2}"
                shift 2
                ;;
            --gadget)
                gadget_snap="${2}"
                shift 2
                ;;
            --gadget-assertion)
                gadget_assertion="${2}"
                shift 2
                ;;
            --kernel)
                kernel_snap="${2}"
                shift 2
                ;;
            --kernel-assertion)
                kernel_assertion="${2}"
                shift 2
                ;;
            --label)
                label="${2}"
                shift 2
                ;;
            --disk)
                disk="${2}"
                shift 2
                ;;
            --kmods-comp)
                # Only on kernel-modules component supported atm
                kern_mods_comp="${2}"
                shift 2
                ;;
            --passphrase)
                passphrase="${2}"
                shift 2
                ;;
            --recovery-key-out)
                recovery_key_out="${2}"
                shift 2
                ;;
            --exit-at-preinstall)
                exit_at_preinstall="true"
                shift 1
                ;;
            --extra-muinstaller-arg)
                extra_muinstaller_args+=("${2}")
                shift 2
                ;;
            --*|-*)
                echo "Unknown option ${1}"
                exit 1
                ;;
            *)
                shift
                ;;
        esac
    done

    if [ -z "${model_assertion}" ]; then
        echo "--model is required"
        exit 1
    fi

    if [ -n "${kernel_snap}" ] && [ -z "${kernel_assertion}" ]; then
        echo "--kernel-assertion is required when --kernel is provided"
        exit 1
    fi

    if [ -n "${kernel_assertion}" ] && [ -z "${kernel_snap}" ]; then
        echo "--kernel is required when --kernel-assertion is provided"
        exit 1
    fi

    if [ -n "${gadget_snap}" ] && [ -z "${gadget_assertion}" ]; then
        echo "--gadget-assertion is required when --gadget is provided"
        exit 1
    fi

    if [ -n "${gadget_assertion}" ] && [ -z "${gadget_snap}" ]; then
        echo "--gadget is required when --gadget-assertion is provided"
        exit 1
    fi

    # since we change directories below, we need to make sure we have absolute
    # paths for all inputs
    model_assertion="$(realpath "${model_assertion}")"

    if [ -n "${disk}" ]; then
        disk="$(realpath "${disk}")"
    fi

    if [ -n "${store_dir}" ]; then
        store_dir="$(realpath "${store_dir}")"
    fi

    if [ -n "${gadget_snap}" ]; then
        gadget_snap="$(realpath "${gadget_snap}")"
        gadget_assertion="$(realpath "${gadget_assertion}")"
    fi

    if [ -n "${kernel_snap}" ]; then
        kernel_snap="$(realpath "${kernel_snap}")"
        kernel_assertion="$(realpath "${kernel_assertion}")"
    fi

    if [ -n "${kern_mods_comp}" ]; then
        kern_mods_comp="$(realpath "${kern_mods_comp}")"
    fi

    # start a subshell and change directories so that we can change directories
    # to keep all of our generated files together
    (
        cd "$(mktemp -d --tmpdir="${PWD}")"

        # create new disk (if the caller didn't provide one) for the installer to
        # work on and attach to the VM
        if [ -z "${disk}" ]; then
            disk="${PWD}/disk.img"
            truncate --size=6G "${disk}"
        fi

        # if a gadget wasn't provided, download one we know should work for hybrid
        # systems
        if [ -z "${gadget_snap}" ]; then
            snap download --channel="classic-23.10/stable" --basename=pc pc
            gadget_snap="${PWD}/pc.snap"
            gadget_assertion="${PWD}/pc.assert"
        fi

        # if a kernel wasn't provided, download one
        if [ -z "${kernel_snap}" ]; then
            snap download --channel="23.10/stable" --basename=pc-kernel pc-kernel
            kernel_snap="${PWD}/pc-kernel.snap"
            kernel_assertion="${PWD}/pc-kernel.assert"
        fi

        run_muinstaller "${model_assertion}" "${store_dir}" "${gadget_snap}" \
                        "${gadget_assertion}" "${kernel_snap}" "${kernel_assertion}" "${label}" \
                        "${disk}" "${kern_mods_comp}" "${passphrase}" "${recovery_key_out}" \
			"${exit_at_preinstall}" \
                        "${extra_muinstaller_args[@]}"
    )
}

main "$@"