Skip to content

Commit 04e7678

Browse files
committed
Add support for persistent disk when booting from ISO image
When booting from a LiveCD image there is no point in generating a diffDisk because the OS is running from RAM, not from the disk. In that case just add diffDisk as an independent data volume and move directories that should be persisted on there. Currently: /home, /usr/local, /etc/containerd, and /var/lib/containerd. This data-volume logic is currently only enabled for Alpine because it hasn't been tested anywhere else yet. Additional, if the configured disk size is 0, no diffDisk will be allocated and changes will be written back to the baseDisk (or discarded, if the baseDisk is an ISO image). Signed-off-by: Jan Dubois <[email protected]>
1 parent 0f25d1d commit 04e7678

File tree

3 files changed

+129
-36
lines changed

3 files changed

+129
-36
lines changed

pkg/cidata/user-data.TEMPLATE

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,75 @@ users:
1818
{{- end}}
1919

2020
write_files:
21+
- content: |
22+
#!/bin/sh
23+
# This script pretends that /bin/ash can be used as /bin/bash, so all following
24+
# cloud-init scripts can use `#!/bin/bash` and `set -o pipefail`.
25+
test -f /etc/alpine-release || exit 0
26+
27+
# Redirect bash to ash (built with CONFIG_ASH_BASH_COMPAT) and hope for the best :)
28+
# It does support `set -o pipefail`, but not `[[`.
29+
# /bin/bash can't be a symlink because /bin/ash is a symlink to /bin/busybox
30+
cat >/bin/bash <<'EOF'
31+
#!/bin/sh
32+
exec /bin/ash "$@"
33+
EOF
34+
chmod +x /bin/bash
35+
owner: root:root
36+
path: /var/lib/cloud/scripts/per-boot/00-alpine-ash-as-bash.boot.sh
37+
permissions: '0755'
38+
- content: |
39+
#!/bin/bash
40+
set -eux -o pipefail
41+
42+
# Restrict the rest of this script to Alpine until it has been tested with other distros
43+
test -f /etc/alpine-release || exit 0
44+
45+
# Data directories that should be persisted across reboots
46+
DATADIRS="/home /usr/local /etc/containerd /var/lib/containerd"
47+
48+
# When running from RAM try to move persistent data to data-volume
49+
# FIXME: the test for tmpfs mounts is probably Alpine-specific
50+
if [ "$(awk '$2 == "/" {print $3}' /proc/mounts)" == "tmpfs" ]; then
51+
mkdir -p /mnt/data
52+
if [ -e /dev/disk/by-label/data-volume ]; then
53+
mount -t ext4 /dev/disk/by-label/data-volume /mnt/data
54+
else
55+
# Find an unpartitioned disk and create data-volume
56+
DISKS=$(lsblk --list --noheadings --output name,type | awk '$2 == "disk" {print $1}')
57+
for DISK in ${DISKS}; do
58+
IN_USE=false
59+
# Looking for a disk that is not mounted or partitioned
60+
for PART in $(awk '/^\/dev\// {gsub("/dev/", ""); print $1}' /proc/mounts); do
61+
if [ "${DISK}" == "${PART}" -o -e /sys/block/${DISK}/${PART} ]; then
62+
IN_USE=true
63+
break
64+
fi
65+
done
66+
if [ "${IN_USE}" == "false" ]; then
67+
echo 'type=83' | sfdisk --label dos /dev/${DISK}
68+
PART=$(lsblk --list /dev/${DISK} --noheadings --output name,type | awk '$2 == "part" {print $1}')
69+
mkfs.ext4 -L data-volume /dev/${PART}
70+
mount -t ext4 /dev/disk/by-label/data-volume /mnt/data
71+
for DIR in ${DATADIRS}; do
72+
DEST="/mnt/data$(dirname ${DIR})"
73+
mkdir -p ${DIR} ${DEST}
74+
mv ${DIR} ${DEST}
75+
done
76+
break
77+
fi
78+
done
79+
fi
80+
for DIR in ${DATADIRS}; do
81+
if [ -d /mnt/data${DIR} ]; then
82+
[ -e ${DIR} ] && rm -rf ${DIR}
83+
ln -s /mnt/data${DIR} ${DIR}
84+
fi
85+
done
86+
fi
87+
owner: root:root
88+
path: /var/lib/cloud/scripts/per-boot/05-persistent-data-volume.boot.sh
89+
permissions: '0755'
2190
- content: |
2291
#!/sbin/openrc-run
2392
supervisor=supervise-daemon
@@ -35,34 +104,24 @@ write_files:
35104
path: /var/lib/lima-guestagent/lima-guestagent.openrc
36105
permissions: '0755'
37106
- content: |
38-
#!/bin/sh
39-
# This script prepares Alpine for lima; there is nothing in here for other distros
40-
test -f /etc/alpine-release || exit
41-
42-
# Since we are on Alpine, we can now assume /bin/sh is /bin/ash
107+
#!/bin/bash
43108
set -eux -o pipefail
44109
45-
# Redirect bash to ash (built with CONFIG_ASH_BASH_COMPAT) and hope for the best :)
46-
# (it does support `set -o pipefail`, but not `[[`)
47-
# /bin/bash can't be a symlink because /bin/ash is a symlink to /bin/busybox
48-
cat >/bin/bash <<'EOF'
49-
#!/bin/sh
50-
exec /bin/ash "$@"
51-
EOF
52-
chmod +x /bin/bash
110+
# This script prepares Alpine for lima; there is nothing in here for other distros
111+
test -f /etc/alpine-release || exit 0
53112
54113
# Configure apk repos
55-
branch=edge
114+
BRANCH=edge
56115
VERSION_ID=$(awk -F= '$1=="VERSION_ID" {print $2}' /etc/os-release)
57-
case $VERSION_ID in
58-
*_alpha*|*_beta*) branch=edge;;
59-
*.*.*) branch=v${VERSION_ID%.*};;
116+
case ${VERSION_ID} in
117+
*_alpha*|*_beta*) BRANCH=edge;;
118+
*.*.*) BRANCH=v${VERSION_ID%.*};;
60119
esac
61120
62-
for repo in main community; do
63-
url="https://dl-cdn.alpinelinux.org/alpine/${branch}/${repo}"
64-
if ! grep -q "^${url}$" /etc/apk/repositories; then
65-
echo "${url}" >> /etc/apk/repositories
121+
for REPO in main community; do
122+
URL="https://dl-cdn.alpinelinux.org/alpine/${BRANCH}/${REPO}"
123+
if ! grep -q "^${URL}$" /etc/apk/repositories; then
124+
echo "${URL}" >> /etc/apk/repositories
66125
fi
67126
done
68127
@@ -85,7 +144,7 @@ write_files:
85144
rc-update add acpid
86145
rc-service acpid start
87146
owner: root:root
88-
path: /var/lib/cloud/scripts/per-boot/00-alpine-prep.boot.sh
147+
path: /var/lib/cloud/scripts/per-boot/10-alpine-prep.boot.sh
89148
permissions: '0755'
90149
- content: |
91150
#!/bin/bash
@@ -159,7 +218,7 @@ write_files:
159218
fi
160219
owner: root:root
161220
# We do not use per-once.
162-
path: /var/lib/cloud/scripts/per-boot/10-base.boot.sh
221+
path: /var/lib/cloud/scripts/per-boot/20-base.boot.sh
163222
permissions: '0755'
164223
{{- if or .Mounts .Containerd.System .Containerd.User }}
165224
- content: |
@@ -210,7 +269,7 @@ write_files:
210269
fi
211270
{{- end}}
212271
owner: root:root
213-
path: /var/lib/cloud/scripts/per-boot/20-install-packages.boot.sh
272+
path: /var/lib/cloud/scripts/per-boot/30-install-packages.boot.sh
214273
permissions: '0755'
215274
{{- end}}
216275
{{- if or .Containerd.System .Containerd.User}}
@@ -274,7 +333,7 @@ write_files:
274333
fi
275334
{{- end}}
276335
owner: root:root
277-
path: /var/lib/cloud/scripts/per-boot/30-install-containerd.boot.sh
336+
path: /var/lib/cloud/scripts/per-boot/40-install-containerd.boot.sh
278337
permissions: '0755'
279338
{{- end}}
280339
{{- if .Provision}}
@@ -291,7 +350,7 @@ write_files:
291350
{{- end}}
292351
{{- end}}
293352
owner: root:root
294-
path: /var/lib/cloud/scripts/per-boot/40-execute-provision-scripts.boot.sh
353+
path: /var/lib/cloud/scripts/per-boot/50-execute-provision-scripts.boot.sh
295354
permissions: '0755'
296355
{{- end}}
297356
{{- range $i, $val := .Provision}}

pkg/iso9660util/iso9660util.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,18 @@ func WriteFile(fs filesystem.FileSystem, path string, r io.Reader) (int64, error
6060
defer f.Close()
6161
return io.Copy(f, r)
6262
}
63+
64+
func IsISO9660(imagePath string) (bool, error) {
65+
imageFile, err := os.Open(imagePath)
66+
if err != nil {
67+
return false, err
68+
}
69+
defer imageFile.Close()
70+
71+
fileInfo, err := imageFile.Stat()
72+
if err != nil {
73+
return false, err
74+
}
75+
_, err = iso9660.Read(imageFile, fileInfo.Size(), 0, 0)
76+
return err == nil, nil
77+
}

pkg/qemu/qemu.go

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strconv"
1010

1111
"github.com/AkihiroSuda/lima/pkg/downloader"
12+
"github.com/AkihiroSuda/lima/pkg/iso9660util"
1213
"github.com/AkihiroSuda/lima/pkg/limayaml"
1314
"github.com/AkihiroSuda/lima/pkg/store/filenames"
1415
"github.com/docker/go-units"
@@ -60,15 +61,20 @@ func EnsureDisk(cfg Config) error {
6061
len(cfg.LimaYAML.Images), errs)
6162
}
6263
}
63-
diskSize, err := units.RAMInBytes(cfg.LimaYAML.Disk)
64+
diskSize, _ := units.RAMInBytes(cfg.LimaYAML.Disk)
65+
if diskSize == 0 {
66+
return nil
67+
}
68+
isBaseDiskISO, err := iso9660util.IsISO9660(baseDisk)
6469
if err != nil {
6570
return err
6671
}
67-
cmd := exec.Command("qemu-img", "create",
68-
"-f", "qcow2",
69-
"-b", baseDisk,
70-
diffDisk,
71-
strconv.Itoa(int(diskSize)))
72+
args := []string{"create", "-f", "qcow2"}
73+
if !isBaseDiskISO {
74+
args = append(args, "-b", baseDisk)
75+
}
76+
args = append(args, diffDisk, strconv.Itoa(int(diskSize)))
77+
cmd := exec.Command("qemu-img", args...)
7278
if out, err := cmd.CombinedOutput(); err != nil {
7379
return errors.Wrapf(err, "failed to run %v: %q", cmd.Args, string(out))
7480
}
@@ -118,11 +124,24 @@ func Cmdline(cfg Config) (string, []string, error) {
118124
} else if y.Arch != limayaml.X8664 {
119125
logrus.Warnf("field `firmware.legacyBIOS` is not supported for architecture %q, ignoring", y.Arch)
120126
}
121-
args = append(args, "-boot", "order=c,splash-time=0,menu=on")
122-
123-
// Root disk
124-
args = append(args, "-drive", fmt.Sprintf("file=%s,if=virtio", filepath.Join(cfg.InstanceDir, filenames.DiffDisk)))
125127

128+
baseDisk := filepath.Join(cfg.InstanceDir, filenames.BaseDisk)
129+
diffDisk := filepath.Join(cfg.InstanceDir, filenames.DiffDisk)
130+
isBaseDiskCDROM, err := iso9660util.IsISO9660(baseDisk)
131+
if err != nil {
132+
return "", nil, err
133+
}
134+
if isBaseDiskCDROM {
135+
args = append(args, "-boot", "order=d,splash-time=0,menu=on")
136+
args = append(args, "-drive", fmt.Sprintf("file=%s,media=cdrom,readonly=on", baseDisk))
137+
} else {
138+
args = append(args, "-boot", "order=c,splash-time=0,menu=on")
139+
}
140+
if diskSize, _ := units.RAMInBytes(cfg.LimaYAML.Disk); diskSize > 0 {
141+
args = append(args, "-drive", fmt.Sprintf("file=%s,if=virtio", diffDisk))
142+
} else if !isBaseDiskCDROM {
143+
args = append(args, "-drive", fmt.Sprintf("file=%s,if=virtio", baseDisk))
144+
}
126145
// cloud-init
127146
args = append(args, "-cdrom", filepath.Join(cfg.InstanceDir, filenames.CIDataISO))
128147

0 commit comments

Comments
 (0)