Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/hack/update-template-debian.sh
1639 views
1
#!/usr/bin/env bash
2
3
# SPDX-FileCopyrightText: Copyright The Lima Authors
4
# SPDX-License-Identifier: Apache-2.0
5
6
set -eu -o pipefail
7
8
# Functions in this script assume error handling with 'set -e'.
9
# To ensure 'set -e' works correctly:
10
# - Use 'set +e' before assignments and '$(set -e; <function>)' to capture output without exiting on errors.
11
# - Avoid calling functions directly in conditions to prevent disabling 'set -e'.
12
# - Use 'shopt -s inherit_errexit' (Bash 4.4+) to avoid repeated 'set -e' in all '$(...)'.
13
shopt -s inherit_errexit || error_exit "inherit_errexit not supported. Please use bash 4.4 or later."
14
15
function debian_print_help() {
16
cat <<HELP
17
$(basename "${BASH_SOURCE[0]}"): Update the Debian image location in the specified templates
18
19
Usage:
20
$(basename "${BASH_SOURCE[0]}") [--backports[=<bool>]] [--daily[=<bool>]] [--timestamped[=<bool>]] [--version <version>] <template.yaml>...
21
22
Description:
23
This script updates the Debian image location in the specified templates.
24
If the image location in the template contains a release date in the URL, the script replaces it with the latest available date.
25
If no flags are specified, the script uses the version from the image location basename in the template.
26
27
Image location basename format: debian-<version>[-backports]-genericcloud-<arch>[-daily][-<timestamp>].qcow2
28
29
Published Debian image information is fetched from the following URLs:
30
31
https://cloud.debian.org/images/cloud/<codename>[-backports]/[daily/](latest|<timestamp>)/debian-<version>[-backports]-genericcloud-<arch>[-daily][-<timestamp>].json
32
33
The downloaded JSON file will be cached in the Lima cache directory.
34
35
Examples:
36
Update the Debian image location in templates/**.yaml:
37
$ $(basename "${BASH_SOURCE[0]}") templates/**.yaml
38
39
Update the Debian image location in ~/.lima/debian/lima.yaml:
40
$ $(basename "${BASH_SOURCE[0]}") ~/.lima/debian/lima.yaml
41
$ limactl factory-reset debian
42
43
Update the Debian image location to debian-13-genericcloud-<arch>.qcow2 in ~/.lima/debian/lima.yaml:
44
$ $(basename "${BASH_SOURCE[0]}") --version trixie ~/.lima/debian/lima.yaml
45
$ limactl factory-reset debian
46
47
Flags:
48
--backports[=<bool>] Use the backports image
49
The boolean value can be true, false, 1, or 0
50
--daily[=<bool>] Use the daily image
51
--timestamped[=<bool>] Use the timestamped image
52
--version <version> Use the specified version
53
The version can be a codename, version number, or alias (testing, stable, oldstable)
54
-h, --help Print this help message
55
HELP
56
}
57
58
readonly debian_base_url=https://cloud.debian.org/images/cloud/
59
60
readonly debian_target_vendor=genericcloud
61
62
readonly -A debian_version_to_codename=(
63
[10]=buster
64
[11]=bullseye
65
[12]=bookworm
66
[13]=trixie
67
[14]=forky
68
)
69
70
declare -A debian_codename_to_version
71
function debian_setup_codename_to_version() {
72
local version codename
73
for version in "${!debian_version_to_codename[@]}"; do
74
codename=${debian_version_to_codename[${version}]}
75
debian_codename_to_version[${codename}]="${version}"
76
done
77
readonly -A debian_codename_to_version
78
}
79
debian_setup_codename_to_version
80
81
readonly -A debian_alias_to_codename=(
82
[testing]=trixie
83
[stable]=bookworm
84
[oldstable]=bullseye
85
)
86
87
# debian_downloaded_json downloads the JSON file for the given url_spec(JSON) and caches it
88
# e.g.
89
# ```console
90
# debian_downloaded_json '{"backports":false,"daily":false,"version":12,"arch":"amd64","file_extension":"qcow2"}'
91
#
92
# ```
93
function debian_downloaded_json() {
94
local url_spec=$1 json_url_spec json_url
95
json_url_spec=$(jq -r '. | del(.timestamp) | .file_extension = "json"' <<<"${url_spec}") || error_exit "Failed to create JSON URL spec"
96
json_url=$(debian_location_from_url_spec "${json_url_spec}")
97
download_to_cache "${json_url}"
98
}
99
100
function debian_digest_from_upload_entry() {
101
local upload_entry=$1 debian_digest digest
102
debian_digest=$(jq -e -r '.metadata.annotations."cloud.debian.org/digest"' <<<"${upload_entry}") ||
103
error_exit "Failed to get the digest from ${upload_entry}"
104
case "${debian_digest%:*}" in
105
sha512) digest=$(echo "${debian_digest#*:}==" | base64 -d | xxd -p -c -) ||
106
error_exit "Failed to decode the digest from ${debian_digest}" ;;
107
*) error_exit "Unsupported digest type: ${debian_digest%:*}" ;;
108
esac
109
echo "${debian_digest/:*/:}${digest}"
110
}
111
112
# debian_image_url_timestamped prints the latest image URL and its digest for the given flavor, version, arch, and path suffix.
113
function debian_image_url_timestamped() {
114
local url_spec=$1 debian_downloaded_json jq_filter upload_entry timestamp timestamped_url_spec location arch digest
115
debian_downloaded_json=$(debian_downloaded_json "${url_spec}")
116
# shellcheck disable=SC2016
117
jq_filter='
118
[.items[]|select(.kind == "Upload")|
119
select(.metadata.labels."upload.cloud.debian.org/image-format" == $ARGS.named.url_spec.image_format)]|first
120
'
121
upload_entry=$(jq -e -r --argjson url_spec "${url_spec}" "${jq_filter}" "${debian_downloaded_json}") ||
122
error_exit "Failed to find the upload entry from ${debian_downloaded_json}"
123
timestamp=$(jq -e -r '.metadata.labels."cloud.debian.org/version"' <<<"${upload_entry}") ||
124
error_exit "Failed to get the timestamp from ${upload_entry}"
125
timestamped_url_spec=$(json_vars timestamp <<<"${url_spec}")
126
location=$(debian_location_from_url_spec "${timestamped_url_spec}")
127
location=$(validate_url_without_redirect "${location}")
128
arch=$(jq -e -r '.arch' <<<"${url_spec}") || error_exit "missing arch in ${url_spec}"
129
arch=$(limayaml_arch "${arch}")
130
digest=$(debian_digest_from_upload_entry "${upload_entry}")
131
json_vars location arch digest
132
}
133
134
# debian_image_url_not_timestamped prints the release image URL for the given url_spec(JSON)
135
function debian_image_url_not_timestamped() {
136
local url_spec=$1 location arch
137
location=$(debian_location_from_url_spec "${url_spec}")
138
location=$(validate_url_without_redirect "${location}")
139
arch=$(jq -e -r '.arch' <<<"${url_spec}") || error_exit "missing arch in ${url_spec}"
140
arch=$(limayaml_arch "${arch}")
141
json_vars location arch
142
}
143
144
# debian_version_resolve_aliases resolves the version aliases.
145
# e.g.
146
# ```console
147
# debian_version_resolve_aliases testing
148
# 13
149
# debian_version_resolve_aliases stable
150
# 12
151
# debian_version_resolve_aliases oldstable
152
# 11
153
# debian_version_resolve_aliases bookworm
154
# 12
155
# debian_version_resolve_aliases 10
156
# 10
157
# debian_version_resolve_aliases ''
158
#
159
# ```
160
function debian_version_resolve_aliases() {
161
local version=$1
162
[[ -v debian_alias_to_codename[${version}] ]] && version=${debian_alias_to_codename[${version}]}
163
[[ -v debian_codename_to_version[${version}] ]] && version=${debian_codename_to_version[${version}]}
164
[[ -v debian_version_to_codename[${version}] ]] || error_exit "Unsupported version: ${version}"
165
[[ -z ${version} ]] || echo "${version}"
166
}
167
168
function debian_arch_from_location_basename() {
169
local location=$1 location_basename arch
170
location_basename=$(basename "${location}")
171
location_basename=${location_basename/-backports/}
172
arch=$(echo "${location_basename}" | cut -d- -f4 | cut -d. -f1)
173
[[ -n ${arch} ]] || error_exit "Failed to get arch from ${location}"
174
echo "${arch}"
175
}
176
177
function debian_file_extension_from_location_basename() {
178
local location=$1 location_basename file_extension
179
location_basename=$(basename "${location}")
180
file_extension=$(echo "${location_basename}" | cut -d. -f2-) # remove the first field
181
[[ -n ${file_extension} ]] || error_exit "Failed to get file extension from ${location}"
182
echo "${file_extension}"
183
}
184
185
function debian_image_format_from_file_extension() {
186
local file_extension=$1
187
case "${file_extension}" in
188
json) echo "json" ;;
189
qcow2) echo "qcow2" ;;
190
raw) echo "raw" ;;
191
tar.xz) echo "internal" ;;
192
*) error_exit "Unsupported file extension: ${file_extension}" ;;
193
esac
194
}
195
196
# debian_url_spec_from_location returns the URL spec for the given location.
197
# If the location is not supported, it returns 1.
198
# e.g.
199
# ```console
200
# debian_url_spec_from_location https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
201
# {"backports":false,"daily":false,"version":12,"arch":"amd64","file_extension":"qcow2","image_format":"qcow2"}
202
# debian_url_spec_from_location https://cloud.debian.org/images/cloud/bookworm/20241004-1890/debian-12-generic-amd64-20241004-1890.qcow2
203
# {"backports":false,"daily":false,"timestamp":"20241019-1905","version":12,"arch":"amd64","file_extension":"qcow2","image_format":"qcow2"}
204
# debian_url_spec_from_location https://cloud.debian.org/images/cloud/bookworm/daily/latest/debian-12-genericcloud-amd64-daily.qcow2
205
# {"backports":false,"daily":true,"version":12,"arch":"amd64","file_extension":"qcow2","image_format":"qcow2"}
206
# debian_url_spec_from_location https://cloud.debian.org/images/cloud/bookworm/daily/20241019-1905/debian-12-genericcloud-amd64-daily-20241019-1905.qcow2
207
# {"backports":false,"daily":true,"timestamp":"20241019-1905","version":12,"arch":"amd64","file_extension":"qcow2","image_format":"qcow2"}
208
# debian_url_spec_from_location https://cloud.debian.org/images/cloud/bookworm-backports/latest/debian-12-backports-genericcloud-amd64.qcow2
209
# {"backports":true,"daily":false,"version":12,"arch":"amd64","file_extension":"qcow2","image_format":"qcow2"}
210
# debian_url_spec_from_location https://cloud.debian.org/images/cloud/bookworm-backports/20241004-1890/debian-12-backports-genericcloud-amd64-20241004-1890.qcow2
211
# {"backports":true,"daily":false,"timestamp":"20241019-1905","version":12,"arch":"amd64","file_extension":"qcow2","image_format":"qcow2"}
212
# debian_url_spec_from_location https://cloud.debian.org/images/cloud/bookworm-backports/daily/latest/debian-12-backports-genericcloud-amd64-daily.qcow2
213
# {"backports":true,"daily":true,"version":12,"arch":"amd64","file_extension":"qcow2","image_format":"qcow2"}
214
# debian_url_spec_from_location https://cloud.debian.org/images/cloud/bookworm-backports/daily/20241019-1905/debian-12-backports-genericcloud-amd64-daily-20241019-1905.qcow2
215
# {"backports":true,"daily":true,"timestamp":"20241019-1905","version":12,"arch":"amd64","file_extension":"qcow2","image_format":"qcow2"}
216
# ```
217
# shellcheck disable=SC2034
218
function debian_url_spec_from_location() {
219
local location=$1 backports=false daily=false timestamp='' codename version='' arch file_extension image_format
220
local -r timestamp_pattern='[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]'
221
case "${location}" in
222
${debian_base_url}*-backports/*) backports=true ;;&
223
${debian_base_url}*/daily/*) daily=true ;;&
224
${debian_base_url}*/${timestamp_pattern}/*) [[ ${location} =~ ${timestamp_pattern} ]] && timestamp=${BASH_REMATCH[0]} ;;
225
${debian_base_url}*/latest/*) timestamp='' ;;
226
*)
227
# echo "Unsupported image location: ${location}" >&2
228
return 1
229
;;
230
esac
231
codename=$(echo "${location#"${debian_base_url}"}" | cut -d/ -f1 | cut -d- -f1)
232
[[ -v debian_codename_to_version[${codename}] ]] || error_exit "Unknown codename: ${codename}"
233
version=${debian_codename_to_version[${codename}]}
234
arch=$(debian_arch_from_location_basename "${location}")
235
file_extension=$(debian_file_extension_from_location_basename "${location}")
236
image_format=$(debian_image_format_from_file_extension "${file_extension}")
237
json_vars backports daily timestamp version arch file_extension image_format
238
}
239
240
# debian_location_from_url_spec returns the location for the given URL spec.
241
# e.g.
242
# ```console
243
# debian_location_from_url_spec '{"backports":false,"daily":false,"version":12,"arch":"amd64","file_extension":"qcow2"}'
244
# https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
245
# debian_location_from_url_spec '{"backports":false,"daily":false,"timestamp":"20241019-1905","version":12,"arch":"amd64","file_extension":"qcow2"}'
246
# https://cloud.debian.org/images/cloud/bookworm/20241019-1905/debian-12-generic-amd64-20241019-1905.qcow2
247
# debian_location_from_url_spec '{"backports":false,"daily":true,"version":12,"arch":"amd64","file_extension":"qcow2"}'
248
# https://cloud.debian.org/images/cloud/bookworm/daily/latest/debian-12-genericcloud-amd64-daily.qcow2
249
# debian_location_from_url_spec '{"backports":false,"daily":true,"timestamp":"20241019-1905","version":12,"arch":"amd64","file_extension":"qcow2"}'
250
# https://cloud.debian.org/images/cloud/bookworm/daily/20241019-1905/debian-12-generic-amd64-daily-20241019-1905.qcow2
251
# debian_location_from_url_spec '{"backports":true,"daily":false,"version":12,"arch":"amd64","file_extension":"qcow2"}'
252
# https://cloud.debian.org/images/cloud/bookworm-backports/latest/debian-12-backports-genericcloud-amd64.qcow2
253
# debian_location_from_url_spec '{"backports":true,"daily":false,"timestamp":"20241019-1905","version":12,"arch":"amd64","file_extension":"qcow2"}'
254
# https://cloud.debian.org/images/cloud/bookworm-backports/20241019-1905/debian-12-backports-genericcloud-amd64-20241019-1905.qcow2
255
# debian_location_from_url_spec '{"backports":true,"daily":true,"version":12,"arch":"amd64","file_extension":"qcow2"}'
256
# https://cloud.debian.org/images/cloud/bookworm-backports/daily/latest/debian-12-backports-genericcloud-amd64-daily.qcow2
257
# debian_location_from_url_spec '{"backports":true,"daily":true,"timestamp":"20241019-1905","version":12,"arch":"amd64","file_extension":"qcow2"}'
258
# https://cloud.debian.org/images/cloud/bookworm-backports/daily/20241019-1905/debian-12-backports-genericcloud-amd64-daily-20241019-1905.qcow2
259
# ```
260
function debian_location_from_url_spec() {
261
local url_spec=$1 base_url version backports daily timestamp arch file_extension
262
base_url=${debian_base_url}
263
version=$(jq -e -r '.version' <<<"${url_spec}")
264
[[ -v debian_version_to_codename[${version}] ]] || error_exit "Unsupported version: ${version}"
265
base_url+=${debian_version_to_codename[${version}]}
266
backports=$(jq -r 'if .backports then "-backports" else empty end' <<<"${url_spec}")
267
base_url+=${backports}/
268
daily=$(jq -r 'if .daily then "daily" else empty end' <<<"${url_spec}")
269
base_url+=${daily:+${daily}/}
270
timestamp=$(jq -r 'if .timestamp then .timestamp else empty end' <<<"${url_spec}")
271
base_url+=${timestamp:-latest}/
272
arch=$(jq -e -r '.arch' <<<"${url_spec}")
273
file_extension=$(jq -e -r '.file_extension' <<<"${url_spec}")
274
base_url+=debian-${version}${backports}-${debian_target_vendor}-${arch}${daily:+-${daily}}${timestamp:+-${timestamp}}.${file_extension}
275
echo "${base_url}"
276
}
277
278
# debian_cache_key_for_image_kernel_overriding returns the cache key for the given location, kernel_location, flavor, and version.
279
# If the image location is not supported, it returns 1.
280
# kernel_location is not validated.
281
# e.g.
282
# ```console
283
# debian_cache_key_for_image_kernel_overriding https://cloud-images.debian.com/minimal/releases/24.04/release-20210914/debian-24.04-minimal-cloudimg-amd64.img
284
# debian_latest_24.04-minimal-amd64-release-.img
285
# debian_cache_key_for_image_kernel_overriding https://cloud-images.debian.com/minimal/releases/24.04/release-20210914/debian-24.04-minimal-cloudimg-amd64.img https://...
286
# debian_latest_with_kernel_24.04-minimal-amd64-release-.img
287
# debian_cache_key_for_image_kernel_overriding https://cloud-images.debian.com/releases/24.04/release/debian-24.04-server-cloudimg-amd64.img null
288
# debian_release_24.04-server-amd64-.img
289
# ```
290
function debian_cache_key_for_image_kernel_overriding() {
291
local location=$1 kernel_location=${2:-null} url_spec with_kernel='' version backports arch daily timestamped file_extension
292
url_spec=$(debian_url_spec_from_location "${location}")
293
[[ ${kernel_location} != "null" ]] && with_kernel=_with_kernel
294
version=$(jq -r '.version|if . then "-\(.)" else empty end' <<<"${url_spec}")
295
backports=$(jq -r 'if .backports then "-backports" else empty end' <<<"${url_spec}")
296
arch=$(jq -e -r '.arch' <<<"${url_spec}")
297
daily=$(jq -r 'if .daily then "-daily" else empty end' <<<"${url_spec}")
298
timestamped=$(jq -r 'if .timestamp then "-timestamped" else empty end' <<<"${url_spec}")
299
file_extension=$(jq -e -r '.file_extension' <<<"${url_spec}")
300
echo "debian${with_kernel}${version}${backports}-${debian_target_vendor}-${arch}${daily}${timestamped}.${file_extension}"
301
}
302
303
function debian_image_entry_for_image_kernel_overriding() {
304
local location=$1 kernel_location=$2 overriding=${3:-"{}"} url_spec timestamped
305
[[ ${kernel_location} == "null" ]] || error_exit "Updating image with kernel is not supported"
306
url_spec=$(debian_url_spec_from_location "${location}" | jq -r ". + ${overriding}")
307
timestamped=$(jq -r 'if .timestamp then "timestamped" else "not_timestamped" end' <<<"${url_spec}")
308
309
local image_entry
310
image_entry=$(debian_image_url_"${timestamped}" "${url_spec}")
311
if [[ -z ${image_entry} ]]; then
312
error_exit "Failed to get the ${url_spec} image location for ${location}"
313
elif jq -e ".location == \"${location}\"" <<<"${image_entry}" >/dev/null; then
314
echo "Image location is up-to-date: ${location}" >&2
315
else
316
echo "${image_entry}"
317
fi
318
}
319
320
# check if the script is executed or sourced
321
# shellcheck disable=SC1091
322
if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
323
scriptdir=$(dirname "${BASH_SOURCE[0]}")
324
# shellcheck source=./cache-common-inc.sh
325
. "${scriptdir}/cache-common-inc.sh"
326
327
# shellcheck source=/dev/null # avoid shellcheck hangs on source looping
328
. "${scriptdir}/update-template.sh"
329
else
330
# this script is sourced
331
if [[ -v SUPPORTED_DISTRIBUTIONS ]]; then
332
SUPPORTED_DISTRIBUTIONS+=("debian")
333
else
334
declare -a SUPPORTED_DISTRIBUTIONS=("debian")
335
fi
336
# required functions for Debian
337
function debian_cache_key_for_image_kernel() { debian_cache_key_for_image_kernel_overriding "$@"; }
338
function debian_image_entry_for_image_kernel() { debian_image_entry_for_image_kernel_overriding "$@"; }
339
340
return 0
341
fi
342
343
declare -a templates=()
344
declare overriding="{}"
345
while [[ $# -gt 0 ]]; do
346
case "$1" in
347
-h | --help)
348
debian_print_help
349
exit 0
350
;;
351
-d | --debug) set -x ;;
352
--backports | --daily | --timestamped)
353
overriding=$(json "${1#--}" true <<<"${overriding}")
354
;;
355
--backports=* | --daily=* | --timestamped=*)
356
overriding=$(
357
key=${1#--} value=$(validate_boolean "${1#*=}")
358
json "${key%%=*}" "${value}" <<<"${overriding}"
359
)
360
;;
361
--version)
362
if [[ -n $2 && $2 != -* ]]; then
363
overriding=$(
364
version=$(debian_version_resolve_aliases "$2")
365
json_vars version <<<"${overriding}"
366
)
367
shift
368
else
369
error_exit "--version requires a value"
370
fi
371
;;
372
--version=*)
373
overriding=$(
374
version=$(debian_version_resolve_aliases "${1#*=}")
375
json_vars version <<<"${overriding}"
376
)
377
;;
378
*.yaml) templates+=("$1") ;;
379
*)
380
error_exit "Unknown argument: $1"
381
;;
382
esac
383
shift
384
[[ -z ${overriding} ]] && overriding="{}"
385
done
386
387
if [[ ${#templates[@]} -eq 0 ]]; then
388
debian_print_help
389
exit 0
390
fi
391
392
declare -A image_entry_cache=()
393
394
for template in "${templates[@]}"; do
395
echo "Processing ${template}"
396
# 1. extract location by parsing template using arch
397
yq_filter="
398
.images[] | [.location, .kernel.location, .kernel.cmdline] | @tsv
399
"
400
parsed=$(yq eval "${yq_filter}" "${template}")
401
402
# 3. get the image location
403
arr=()
404
while IFS= read -r line; do arr+=("${line}"); done <<<"${parsed}"
405
locations=("${arr[@]}")
406
for ((index = 0; index < ${#locations[@]}; index++)); do
407
[[ ${locations[index]} != "null" ]] || continue
408
set -e
409
IFS=$'\t' read -r location kernel_location kernel_cmdline <<<"${locations[index]}"
410
set +e # Disable 'set -e' to avoid exiting on error for the next assignment.
411
cache_key=$(
412
set -e # Enable 'set -e' for the next command.
413
debian_cache_key_for_image_kernel_overriding "${location}" "${kernel_location}"
414
) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.
415
# shellcheck disable=2181
416
[[ $? -eq 0 ]] || continue
417
image_entry=$(
418
set -e # Enable 'set -e' for the next command.
419
if [[ -v image_entry_cache[${cache_key}] ]]; then
420
echo "${image_entry_cache[${cache_key}]}"
421
else
422
debian_image_entry_for_image_kernel_overriding "${location}" "${kernel_location}" "${overriding}"
423
fi
424
) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.
425
# shellcheck disable=2181
426
[[ $? -eq 0 ]] || continue
427
set -e
428
image_entry_cache[${cache_key}]="${image_entry}"
429
if [[ -n ${image_entry} ]]; then
430
[[ ${kernel_cmdline} != "null" ]] &&
431
jq -e 'has("kernel")' <<<"${image_entry}" >/dev/null &&
432
image_entry=$(jq ".kernel.cmdline = \"${kernel_cmdline}\"" <<<"${image_entry}")
433
echo "${image_entry}" | jq
434
limactl edit --log-level error --set "
435
.images[${index}] = ${image_entry}|
436
(.images[${index}] | ..) style = \"double\"
437
" "${template}"
438
fi
439
done
440
done
441
442