使用Distrobuilder构建Incus容器镜像

在拥有了自己的Distrobuilder后(还没有?参见:为Incus构建Distrobuilder),我们来一起构建用于Incus的容器镜像,实现容器镜像自由吧!

一定会有人疑惑:既然已经有了LXC镜像站,为什么还需要自己构建镜像呢?

这是因为,本文希望描述一个全程可控的,避免过度抽象细节的,创建Incus容器镜像的方法。

预先准备

需要在系统上安装distrobuilder,可以使用snap下载,也可以自己构建:为Incus构建Distrobuilder


案例1: Alpine Linux镜像

准备根文件系统(rootfs)

既然我们在讨论可控的构建过程,那么这一定包括一个可控的根文件系统(Root FileSystem, rootfs)。

让我们打开downloads | Alpine Linux,下拉找到MINI ROOT FILESYSTEM项目,下载对应架构的根文件系统。

例如,对于64位系统来说,我们将需要下载alpine-minirootfs-3.20.2-x86_64.tar.gz文件。中间的版本号请不要介意。

能不能用镜像站完成这一下载呢?又或者能不能用自己打包的Alpine Linux根文件系统呢?当然可以!这正是可控过程的魅力所在。

当然,distrobuilder实际上也已将下载rootfs的过程封装在程序中。就Alpine Linux来说,distrobuilder下载的rootfs和我们现在手动下载的是同一个文件。

如果更希望让distrobuilder代劳,请跳过当前这一步骤,后面会再展开介绍。

准备构建配置模板

构建镜像时需要的许多信息都由模板文件提供,如镜像的名称、类型、发行版,还有需要安装的软件包、执行的动作等等。

在distrobuilder中,镜像的模板文件是一个YAML文件,标准格式在官方仓库doc/examples/scheme.yaml中给出,文末也附有该文件复制过来的内容:

只不过,格式文件里,有非常多对键值。看起来一头雾水对吧?没关系,加法做不通我们可以做减法。

转到lxc/lxc-ci/images,这一目录下的镜像配置文件,就是Incus官方镜像服务器上,镜像构建所采用的配置文件。我们目前正在构建Alpine Linux,所以我们直接复制alpine.yaml文件中的内容到本地。由于内容太长,同样地,原始内容放在文末。

这文件也太长了吧?而且仔细一看,还有很多似乎我们不太用得上的配置项,例如适用于虚拟机镜像的命令,在容器中是用不到的。因此,让我们来进一步修改这些配置。

修改构建配置文件

Image: 容器镜像配置

首先,文件的第一节image部分,主要定义了镜像的名称和版本号等信息。

为了避免与官方构建的Alpine Linux镜像名称冲突,我们可以稍微修改一下这部分内容:

1
2
3
4
image:
distribution: "Custom Alpinelinux"
name: custom-alpine
release: 3.20

Source: 镜像rootfs来源配置

配置文件第二节,source部分,主要定义了用于构建容器镜像的rootfs如何获得。由于各Linux发行版的rootfs定义与下载地址都有差异,因此distrobuilder预定义了许多Source,用于解析各种操作系统的rootfs下载规则。

对于Alpine Linux,distrobuilder已经定义好了alpinelinux-http这一Source,因此如果希望distrobuilder帮我们自动下载rootfs,此处可定义如下:

1
2
3
4
source:
downloader: alpinelinux-http
same_as: 3.12
url: https://mirrors.hust.edu.cn/alpine/

其中,URL可以是任意Alpine Linux镜像站,以上例子使用了华中科大镜像站。不过,URL也可以file://开头,指向本地已经下载好的rootfs文件包。

如你所见,示例配置文件包含了一大段GPG公钥,用于验证下载的rootfs。如果信任下载源与下载文件完整性,并希望删掉keys部分以缩简配置文件,请在source节内加上skip_verification: true选项。

不过,既然我们正在尽可能追求可控,我们一定会想:

  • alpinelinux-http源在下载rootfs文件之外,是否还有额外的操作/Tricks?
  • 不考虑后文安装软件包等定制操作,在当前这一步是否有通用于所有Linux发行版rootfs的方法?
  • 是否可以利用已预先准备好的rootfs?

尽管根据sources/alpine-http.go源代码,就目前来说,并没有什么下载之外的额外小Tricks。但到目前为止,distrobuilder最新版3.0并没有直接提供使用现有rootfs目录直接构建容器系统的选项。如果直接在配置文件中删除source一节,进行构建时也会报错。

但是,distrobuilder的确提供了一种使用任意rootfs文件包的Source,即rootfs-http,相关源代码可见sources/rootfs-http.go。简单来说只需要提供一个指向rootfs包的URL,本地网络皆可。

因此,上述source节也可以改为(使用file://指向本地文件时需要使用绝对路径):

1
2
3
source:
downloader: rootfs-http
url: file:///absolute/path/to/alpine-minirootfs-3.20.2-x86_64.tar.gz

Target: 目标特定配置

如果仅希望构建Incus容器镜像,不需要LXC镜像和虚拟机镜像,可以直接把这一节大幅精简:

1
2
targets:
incus:

如有需要,请直接参阅Targets - distrobuilder documentation官方文档。

Files: 文件生成规则配置

和上面类似地,如果不需要Incus虚拟机镜像,仅仅需要保留用于Incus容器的部分,可以直接删除掉所有包含以下标记的虚拟机专用文件生成规则。

1
2
types:
- vm

Packages: 软件包安装预定义配置

这部分将定义Incus在构建镜像时,需要使用何种包管理器,安装哪些软件包,大部分内容可以直接照抄示例。同上,可以删掉所有虚拟机限定的配置。

重点讨论一下软件源相关配置。官方手册中,与定制软件源相关的选项位于packages节下,可以通过添加repositories节设置额外的软件源。例如:

1
2
3
4
5
6
7
8
packages:
# Other parameters
# ...
repositories:
- name: hust
url: |-
https://mirrors.hust.edu.cn/alpine/v3.20/main
https://mirrors.hust.edu.cn/alpine/v3.20/community

但是,这种方法会将新软件源附加到原本的软件源后,并不会替换掉速度可能非常缓慢的官方软件源。

如果希望替换掉rootfs中的官方软件源,我们可以不采用这种方案,请往下看。

Actions: 自定义操作

在这一部分,我们可以定义在容器构建的不同的时机要执行的操作(Action)

目前,distrobuilder提供的执行时机(触发器/Trigger)包括:

  • post-unpack: 在rootfs被解压释放后执行。
  • post-update: 如果packages.updatetrue,在包管理器完成更新后执行。
  • post-packages: 在包管理器安装完所有指定的包后执行。
  • post-files: 在files配置块执行完成后执行。这一部分默认仅在build-lxc, build-incuspack-lxcpack-incus操作中执行。如果希望在build-dir操作中也执行这一块,需要指定--with-post-files选项。

例如,如果我们希望把rootfs中官方的软件源配置为镜像站,则可以添加一个post-unpack操作,内容如下:

1
2
3
4
5
6
actions:
- trigger: post-unpack
action: |-
#!/bin/sh
set -eux
sed -i 's/dl-cdn.alpinelinux.org/mirrors.hust.edu.cn/g' /etc/apk/repositories

该命令会在rootfs解压挂载后,容器执行apk update更新软件源之前执行。

Mappings: 架构名称映射配置

这部分主要是告诉Distrobuilder如何处理不同架构名称之间的映射关系,如amd64x86_64aarch64arm64等。

这部分按照模板直接保留就行,详情见Mappings - distrobuilder documentation官方文档。

不得不感慨一下,Linux由于各种历史包袱和不一致的规范,有时会略显混乱。

最终配置文件

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
image:
name: custom-alpine
distribution: "Custom Alpinelinux"
release: 3.20

source:
downloader: rootfs-http
url: file:///absolute/path/to/alpine-minirootfs-3.20.2-x86_64.tar.gz

targets:
incus:

files:
- path: /etc/hostname
generator: hostname

- path: /etc/hosts
generator: hosts

- path: /etc/network/interfaces
generator: dump
content: |-
auto eth0
iface eth0 inet dhcp
hostname $(hostname)

- path: /etc/inittab
generator: dump
content: |-
# /etc/inittab
::sysinit:/sbin/openrc sysinit
::sysinit:/sbin/openrc boot
::wait:/sbin/openrc default

# Set up a couple of getty's
::respawn:/sbin/getty 38400 console
tty1::respawn:/sbin/getty 38400 tty1
tty2::respawn:/sbin/getty 38400 tty2
tty3::respawn:/sbin/getty 38400 tty3
tty4::respawn:/sbin/getty 38400 tty4

# Stuff to do for the 3-finger salute
::ctrlaltdel:/sbin/reboot

# Stuff to do before rebooting
::shutdown:/sbin/openrc shutdown

- path: /etc/inittab
generator: template
name: inittab
content: |-
# /etc/inittab
::sysinit:/sbin/openrc sysinit
::sysinit:/sbin/openrc boot
::wait:/sbin/openrc default

# Set up a couple of getty's
::respawn:/sbin/getty 38400 console

# Stuff to do for the 3-finger salute
::ctrlaltdel:/sbin/reboot

# Stuff to do before rebooting
::shutdown:/sbin/openrc shutdown

- name: meta-data
generator: cloud-init
variants:
- cloud

- name: network-config
generator: cloud-init
content: |-
version: 1
config:
- type: physical
name: eth0
subnets:
- type: dhcp
control: auto
variants:
- cloud

- name: user-data
generator: cloud-init
variants:
- cloud

- name: vendor-data
generator: cloud-init
variants:
- cloud

packages:
manager: apk
update: true
cleanup: true
sets:
- packages:
- alpine-base
- logrotate
- doas
action: install

- packages:
- cloud-init
- openssh
- e2fsprogs-extra
action: install
variants:
- cloud

- packages:
- py3-pyserial
- py3-netifaces
action: install
variants:
- cloud
releases:
- 3.17
- 3.18
- 3.19
- 3.20
- edge

actions:
- trigger: post-unpack
action: |-
#!/bin/sh
set -eux
sed -i 's/dl-cdn.alpinelinux.org/mirrors.hust.edu.cn/g' /etc/apk/repositories

- trigger: post-packages
action: |-
#!/bin/sh
set -eux

rm -f /var/cache/apk/*

- trigger: post-packages
action: |-
#!/bin/sh
set -eux

# Rewrite configuration for LXC
sed -i 's/#rc_sys=""/rc_sys="lxc"/' /etc/rc.conf

# Honor fstab by not making the localmount script a noop
sed -i 's/-lxc//' /etc/init.d/localmount

# Enable services
for svc_name in bootmisc syslog devfs; do
ln -s /etc/init.d/${svc_name} /etc/runlevels/boot/${svc_name}
done

for svc_name in networking crond; do
ln -s /etc/init.d/${svc_name} /etc/runlevels/default/${svc_name}
done
types:
- container

- trigger: post-files
action: |-
#!/bin/sh
set -eux

setup-cloud-init
variants:
- cloud

mappings:
architecture_map: alpinelinux

我们将最终的配置文件保存,随便起个名字,比方说alpine.yaml即可。

开始构建

新建rootfsimages两个文件夹,分别用于暂存解压的rootfs,以及构建生成的镜像文件。

1
2
mkdir rootfs
mkdir images

首先需要执行的是build-dir指令。

1
sudo distrobuilder build-dir alpine.yaml rootfs

再接着执行pack-incus指令。

1
sudo distrobuilder pack-incus alpine.yaml rootfs images

等待一切完成,images文件夹下就存放着构建好的容器镜像了。

此时一定有人会问:为什么要分两步?为什么不直接执行build-incus指令?

答案当然是可以的。

1
sudo distrobuilder build-incus alpine.yaml images

有什么区别呢?我们来看看Distrobuilder开发者在论坛中的讨论

Oops, I said build instead of pack here as that’s likely what you actually want to use.build-incus is equivalent of build-dir + pack-incus.

The reason why we do it in two steps in our CI is because we produce both LXC and Incus images so that lets us use the same rootfs for both. But if all you care about is the LXD/Incus image, just use build-incus directly.

导入Incus并开始使用

拥有了镜像,只需要导入Incus就可以开始使用了。

1
incus image import images/incus.tar.xz images/rootfs.squashfs --alias custom-alpine/3.20

命令末尾的--alias选项用于给镜像定义一个别名。如果没有别名,我们就只能用镜像的哈希指代镜像了。

接着创建实例,启动镜像。

1
2
incus launch local:custom-alpine/3.20 test-alp
incus exec test-alp -- /bin/ash

附录

配置文件

doc/examples/scheme.yaml

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
# This example contains every possible key

image:
description: |-
here goes the image description
distribution: distro
release: release
architecture: x86_64
expiry: 30d
variant: default
name: distro-release-x86_64
serial: some-random-string

source:
downloader: ubuntu-http
url: http://archive.ubuntu.com
keys:
- 0xdeadbeaf
keyserver: http://keyserver.ubuntu.com
variant: default
suite: suite
same_as: bionic
skip_verification: false
components:
- main

targets:
lxc:
create_message: |-
You just created an {{ image.description }} container.

To enable SSH, run: apt install openssh-server
No default root or user password are set by LXC.

config:
- type: all
before: 5
content: |-
lxc.include = LXC_TEMPLATE_CONFIG/ubuntu.common.conf

- type: user
before: 5
content: |-
lxc.include = LXC_TEMPLATE_CONFIG/ubuntu.userns.conf

- type: all
after: 4
content: |-
lxc.include = LXC_TEMPLATE_CONFIG/common.conf

- type: user
after: 4
content: |-
lxc.include = LXC_TEMPLATE_CONFIG/userns.conf

- type: all
content: |-
lxc.arch = {{ image.architecture_personality }}
incus:
vm:
size: 2147483648
filesystem: ext4

files:
- generator: dump
path: /some/path
content: |-
here goes the content
name: name
mode: 0644
gid: 1000
uid: 1000
template:
properties:
key: value
when:
- always
templated: true
releases:
- a
- b
architectures:
- x86_64
variants:
- default

- generator: hostname
path: /etc/hostname

- generator: hosts
path: /etc/hosts

- generator: remove
path: /root/file

- generator: template
name: foo
content: |-
Here goes the content
template:
properties:
key: value
when:
- create
- copy

packages:
manager: apt
custom_manager:
clean:
cmd: mgr
flags:
- clean
install:
cmd: mgr
flags:
- install
remove:
cmd: mgr
flags:
- remove
refresh:
cmd: mgr
flags:
- refresh
update:
cmd: mgr
flags:
- update
flags:
- --yes
update: true
cleanup: false
sets:
- packages:
- gnupg
action: install
early: true

- packages:
- vim
action: install
releases:
- a
- b
architectures:
- x86_64
variants:
- default

- packages:
- lightdm
action: install
flags:
- --no-install-recommends

- packages:
- grub
action: remove

repositories:
- name: reponame
url: |-
deb http://archive.ubuntu.com/ubuntu {{ image.release }}-updates main restricted universe multiverse
type: type
key: 0xdeadbeaf
releases:
- a
- b
architectures:
- x86_64
variants:
- default

actions:
- trigger: post-unpack
action: |-
#!/bin/sh

do something after the rootfs has been unpacked

- trigger: post-files
action: |-
#!/bin/sh

do something after the files section has been processed

- trigger: post-update
action: |-
#!/bin/sh

do something after packages have been processed

- trigger: post-packages
action: |-
#!/bin/sh

do something after the packages section has been processed

releases:
- a
- b
architectures:
- x86_64
variants:
- default

mappings:
architectures:
a: b
c: d
architecture_map: debian

environment:
clear_defaults: true
variables:
- key: FOO
value: bar

lxc/lxc-ci/images/alpine.yaml

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
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
image:
distribution: "alpinelinux"

source:
downloader: alpinelinux-http
same_as: 3.12
url: https://mirror.csclub.uwaterloo.ca/alpine/
keys:
# 0482D84022F52DF1C4E7CD43293ACD0907D9495A
- |-
-----BEGIN PGP PUBLIC KEY BLOCK-----

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
bcgRRcklNr8mTzJH
=ckew
-----END PGP PUBLIC KEY BLOCK-----

targets:
lxc:
create_message: |
You just created an {{ image.description }} container.

config:
- type: all
before: 5
content: |-
lxc.include = LXC_TEMPLATE_CONFIG/alpine.common.conf
- type: user
before: 5
content: |-
lxc.include = LXC_TEMPLATE_CONFIG/alpine.userns.conf
- type: all
after: 4
content: |-
lxc.include = LXC_TEMPLATE_CONFIG/common.conf
- type: user
after: 4
content: |-
lxc.include = LXC_TEMPLATE_CONFIG/userns.conf
- type: all
content: |-
lxc.arch = {{ image.architecture_personality }}
incus:
vm:
filesystem: ext4

files:
- path: /etc/hostname
generator: hostname

- path: /etc/hosts
generator: hosts

- generator: fstab
types:
- vm

- generator: incus-agent
types:
- vm

- path: /etc/default/grub
generator: dump
pongo: true
content: |-
# Set the recordfail timeout
GRUB_RECORDFAIL_TIMEOUT=0

# Do not wait on grub prompt
GRUB_TIMEOUT=0

# Set the default commandline
GRUB_CMDLINE_LINUX_DEFAULT="console=tty1 console=ttyS0 modules=sd-mod,usb-storage,{{ targets.incus.vm.filesystem }} rootfstype={{ targets.incus.vm.filesystem }}"

# Set the grub console type
GRUB_TERMINAL=console

# Disable os-prober
GRUB_DISABLE_OS_PROBER=true
types:
- vm

- path: /etc/network/interfaces
generator: dump
content: |-
auto eth0
iface eth0 inet dhcp
hostname $(hostname)

- path: /etc/inittab
generator: dump
content: |-
# /etc/inittab
::sysinit:/sbin/openrc sysinit
::sysinit:/sbin/openrc boot
::wait:/sbin/openrc default

# Set up a couple of getty's
::respawn:/sbin/getty 38400 console
tty1::respawn:/sbin/getty 38400 tty1
tty2::respawn:/sbin/getty 38400 tty2
tty3::respawn:/sbin/getty 38400 tty3
tty4::respawn:/sbin/getty 38400 tty4

# Stuff to do for the 3-finger salute
::ctrlaltdel:/sbin/reboot

# Stuff to do before rebooting
::shutdown:/sbin/openrc shutdown

- path: /etc/inittab
generator: template
name: inittab
content: |-
# /etc/inittab
::sysinit:/sbin/openrc sysinit
::sysinit:/sbin/openrc boot
::wait:/sbin/openrc default

# Set up a couple of getty's
::respawn:/sbin/getty 38400 console

# Stuff to do for the 3-finger salute
::ctrlaltdel:/sbin/reboot

# Stuff to do before rebooting
::shutdown:/sbin/openrc shutdown

- name: meta-data
generator: cloud-init
variants:
- cloud

- name: network-config
generator: cloud-init
content: |-
version: 1
config:
- type: physical
name: eth0
subnets:
- type: dhcp
control: auto
variants:
- cloud

- name: user-data
generator: cloud-init
variants:
- cloud

- name: vendor-data
generator: cloud-init
variants:
- cloud

packages:
manager: apk
update: true
cleanup: true
sets:
- packages:
- alpine-base
- logrotate
- doas
action: install

- packages:
- cloud-init
- openssh
- e2fsprogs-extra
action: install
variants:
- cloud

- packages:
- py3-pyserial
- py3-netifaces
action: install
variants:
- cloud
releases:
- 3.17
- 3.18
- 3.19
- 3.20
- edge

- packages:
- acpi
- gcc
- grub-efi
- linux-virt
- udev
action: install
types:
- vm

actions:
- trigger: post-packages
action: |-
#!/bin/sh
set -eux

rm -f /var/cache/apk/*

- trigger: post-packages
action: |-
#!/bin/sh
set -eux

# Rewrite configuration for LXC
sed -i 's/#rc_sys=""/rc_sys="lxc"/' /etc/rc.conf

# Honor fstab by not making the localmount script a noop
sed -i 's/-lxc//' /etc/init.d/localmount

# Enable services
for svc_name in bootmisc syslog devfs; do
ln -s /etc/init.d/${svc_name} /etc/runlevels/boot/${svc_name}
done

for svc_name in networking crond; do
ln -s /etc/init.d/${svc_name} /etc/runlevels/default/${svc_name}
done
types:
- container

- trigger: post-files
action: |-
#!/bin/sh
set -eux

for svc_name in acpid bootmisc hostname hwclock loadkmap modules networking swap sysctl syslog urandom; do
ln -fs /etc/init.d/${svc_name} /etc/runlevels/boot/${svc_name}
done

for svc_name in devfs dmesg hwdrivers mdev udev udev-settle udev-trigger; do
ln -fs /etc/init.d/${svc_name} /etc/runlevels/sysinit/${svc_name}
done

target=/boot/grub/grub.cfg
grub-mkconfig -o "${target}"
sed -i "s#root=[^ ]*#root=${DISTROBUILDER_ROOT_UUID}#g" "${target}"

TARGET="x86_64"
[ "$(uname -m)" = "aarch64" ] && TARGET="arm64"
grub-install --target=${TARGET}-efi --no-nvram --removable
grub-install --target=${TARGET}-efi --no-nvram

# Attempt to correct errors in the installation of all packages.
apk fix
types:
- vm

- trigger: post-files
action: |-
#!/bin/sh
set -eux

for svc_name in sshd; do
ln -fs /etc/init.d/${svc_name} /etc/runlevels/default/${svc_name}
done

# Tweak to prevent cloud-init from getting stuck.
echo "datasource_list: ['NoCloud', 'ConfigDrive', 'LXD', 'None']" > /etc/cloud/cloud.cfg.d/99_lxc.cfg
types:
- vm
variants:
- cloud

- trigger: post-files
action: |-
#!/bin/sh
set -eux

setup-cloud-init
variants:
- cloud

mappings:
architecture_map: alpinelinux