blob: 8640ed62ae18e89df7a07dd7da16e662b90da7a8 (
plain) (
blame)
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
|
#!/usr/bin/env bash
set -euo pipefail
URI="qemu:///system"
IMGROOT="/var/lib/libvirt/images"
BASEDIR="$IMGROOT/base"
LABDIR="$IMGROOT/lab"
# Template domains (XML donors)
TEMPLATE_DOMAIN_DEBIAN="debian13-template"
TEMPLATE_DOMAIN_NIXOS="nixos-template"
# Base qcow2 images (backing files)
TEMPLATE_BASE_DEBIAN="$BASEDIR/debian13-template.qcow2"
TEMPLATE_BASE_NIXOS="$BASEDIR/nixos-base.qcow2"
usage() {
cat <<'EOF'
Usage: kvm-lab-create <lab-name> [--base template|work01|nixos|/abs/path.qcow2] [--start]
Examples:
kvm-lab-create lab10 --base template --start
kvm-lab-create lab11 --base work01 --start
kvm-lab-create lab20 --base nixos --start
kvm-lab-create lab12 --base /var/lib/libvirt/images/base/custom.qcow2 --start
EOF
}
# --- Parse args ---
NAME="${1:-}"
shift || true
BASESEL="template"
START="no"
while [[ $# -gt 0 ]]; do
case "$1" in
--base)
BASESEL="${2:-}"
shift 2
;;
--start)
START="yes"
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "ERROR: Unknown argument: $1"
usage
exit 1
;;
esac
done
if [[ -z "${NAME:-}" ]]; then
usage
exit 1
fi
DISK="${LABDIR}/${NAME}.qcow2"
XML_TMP="$(mktemp "/tmp/${NAME}.xml.XXXXXX")"
cleanup() { rm -f "$XML_TMP"; }
trap cleanup EXIT
# --- Resolve base selection (qcow2 backing file) ---
resolve_base() {
local sel="$1"
if [[ "$sel" == "template" ]]; then
echo "$TEMPLATE_BASE_DEBIAN"
return 0
fi
if [[ "$sel" == "nixos" ]]; then
echo "$TEMPLATE_BASE_NIXOS"
return 0
fi
if [[ "$sel" == "work01" ]]; then
# Pick newest promoted work01 base (must exist first)
local latest
latest="$(ls -1t "$BASEDIR"/work01-base-*.qcow2 2>/dev/null | head -n 1 || true)"
if [[ -z "$latest" ]]; then
echo "ERROR: No work01 base found in $BASEDIR (expected work01-base-*.qcow2)."
echo "Action: run your promote script to create one."
return 1
fi
echo "$latest"
return 0
fi
# Absolute path base
if [[ "$sel" == /* ]]; then
echo "$sel"
return 0
fi
echo "ERROR: Invalid --base '$sel' (use template|work01|nixos|/abs/path.qcow2)"
return 1
}
# --- Select template domain (XML donor) based on base selection ---
resolve_template_domain() {
local sel="$1"
if [[ "$sel" == "nixos" ]]; then
echo "$TEMPLATE_DOMAIN_NIXOS"
return 0
fi
# If user gave an absolute qcow2, we default to Debian template unless they explicitly chose nixos.
# (You can change this policy later by adding a separate --os flag.)
echo "$TEMPLATE_DOMAIN_DEBIAN"
return 0
}
BASE="$(resolve_base "$BASESEL")"
TEMPLATE_DOMAIN="$(resolve_template_domain "$BASESEL")"
# --- Preconditions (read-only checks) ---
if [[ ! -d "$LABDIR" ]]; then
echo "ERROR: Lab directory missing: $LABDIR"
exit 1
fi
if ! sudo test -r "$BASE"; then
echo "ERROR: Base image not readable (via sudo): $BASE"
exit 1
fi
if [[ -e "$DISK" ]]; then
echo "ERROR: Disk already exists: $DISK"
exit 1
fi
if virsh -c "$URI" dominfo "$NAME" &>/dev/null; then
echo "ERROR: Domain already exists: $NAME"
exit 1
fi
if ! virsh -c "$URI" dominfo "$TEMPLATE_DOMAIN" &>/dev/null; then
echo "ERROR: Template domain not found in $URI: $TEMPLATE_DOMAIN"
echo "Action:"
if [[ "$TEMPLATE_DOMAIN" == "$TEMPLATE_DOMAIN_NIXOS" ]]; then
echo " - Define a nixos-template domain (UEFI/OVMF) and keep it powered off."
else
echo " - Ensure debian13-template exists."
fi
exit 1
fi
# --- Create overlay disk ---
echo "Using template domain: $TEMPLATE_DOMAIN"
echo "Using base: $BASE"
echo "Creating overlay disk: $DISK"
sudo qemu-img create -f qcow2 -F qcow2 -b "$BASE" "$DISK" >/dev/null
sudo chown libvirt-qemu:libvirt "$DISK"
sudo chmod 0660 "$DISK"
echo "Validating backing file..."
sudo qemu-img info "$DISK" | grep -E "file format:|virtual size:|backing file:" || true
# --- Clone domain XML ---
echo "Cloning domain XML from: $TEMPLATE_DOMAIN"
virsh -c "$URI" dumpxml "$TEMPLATE_DOMAIN" > "$XML_TMP"
# Detect original disk path inside the template XML and replace it robustly
ORIG_DISK_PATH="$(grep -oP "(?<=<source file=')[^']+" "$XML_TMP" | head -n 1 || true)"
if [[ -z "$ORIG_DISK_PATH" ]]; then
echo "ERROR: Could not find <source file='...'> disk path in template XML ($TEMPLATE_DOMAIN)."
echo "Action: ensure the template VM uses a file-backed qcow2 disk."
exit 1
fi
# Set name, disk path, and force NAT-only network "default"
sed -i \
-e "s|<name>${TEMPLATE_DOMAIN}</name>|<name>${NAME}</name>|" \
-e "s|${ORIG_DISK_PATH}|${DISK}|g" \
-e "s|<source network='[^']*'|<source network='default'|g" \
"$XML_TMP"
# Remove UUID to avoid collision with template
sed -i -E '/<uuid>[^<]*<\/uuid>/d' "$XML_TMP"
# Remove any host filesystem passthrough (if present)
perl -0777 -i -pe 's/<filesystem[\s\S]*?<\/filesystem>\n?//g' "$XML_TMP"
# --- Define domain ---
echo "Defining domain: $NAME"
virsh -c "$URI" define "$XML_TMP" >/dev/null
# --- Optional start ---
if [[ "$START" == "yes" ]]; then
echo "Starting domain: $NAME"
virsh -c "$URI" start "$NAME" >/dev/null
fi
echo "OK: Lab '$NAME' created (disk: $DISK)."
if [[ "$START" != "yes" ]]; then
echo "To start: virsh -c $URI start $NAME"
fi
|