blob: 43d2926164ad1d50d5598ea87c090a1d08998149 (
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
|
#!/usr/bin/env bash
set -euo pipefail
URI="qemu:///system"
IMGROOT="/var/lib/libvirt/images"
BASEDIR="$IMGROOT/base"
LABDIR="$IMGROOT/lab"
TEMPLATE_DOMAIN="debian13-template"
TEMPLATE_BASE="$BASEDIR/debian13-template.qcow2"
usage() {
echo "Usage: kvm-lab-create <lab-name> [--base template|work01|/abs/path.qcow2] [--start]"
echo "Examples:"
echo " kvm-lab-create lab10 --base template --start"
echo " kvm-lab-create lab11 --base work01 --start"
echo " kvm-lab-create lab12 --base /var/lib/libvirt/images/base/custom.qcow2 --start"
}
# --- 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 ---
resolve_base() {
local sel="$1"
if [[ "$sel" == "template" ]]; then
echo "$TEMPLATE_BASE"
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|/abs/path.qcow2)"
return 1
}
BASE="$(resolve_base "$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"
exit 1
fi
# --- Create overlay disk ---
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"
# Set name, disk path, and force NAT-only network "default"
sed -i \
-e "s|<name>${TEMPLATE_DOMAIN}</name>|<name>${NAME}</name>|" \
-e "s|${TEMPLATE_BASE}|${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
|