-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathrun-packer.sh
More file actions
executable file
·334 lines (286 loc) · 11.5 KB
/
run-packer.sh
File metadata and controls
executable file
·334 lines (286 loc) · 11.5 KB
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
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Usage: ./run-packer.sh [options]
NOTE: Defaults to /qemu as main working directory
Options:
--docker Run packer in a docker container
--config config for ZFS-root.sh - default ZFS-root.conf.packerci
--discenc VALUE (e.g. NOENC, ZFSENC, LUKS)
--ubuntu-version VALUE (e.g. 24.04.2)
--ubuntu-name VALUE (e.g. noble) [optional; auto-derived from version if not provided]
--output-prefix VALUE (e.g. /qemu/builds/)
--disk-size VALUE (e.g. 5G)
--disks VALUE (e.g. 3) [optional total; for multiple disks]
--raidlevel VALUE (e.g. raidz1 or mirror) only for multiple disks
--secureboot Enable SecureBoot (requires q35 machine and secboot OVMF firmware)
Also sets SECUREBOOT=y for the ZFS-root.sh config
Must manually set "--set AUTOSIGN=y" if auto-signing of boot files required
--iso-src VALUE (e.g. file:///qemu/ISOs) defaults to download
--set KEY=VALUE Override config variables (can be used multiple times)
--help Show this help
For local ISOs, each ISO should be in the appropriate release-named dir
⬇⬇⬇⬇⬇
/qemu/ISOs/focal/ubuntu-20.04.5-live-server-amd64.iso
/qemu/ISOs/jammy/ubuntu-22.04.5-live-server-amd64.iso
/qemu/ISOs/noble/ubuntu-24.04.2-live-server-amd64.iso
/qemu/ISOs/plucky/ubuntu-25.04-live-server-amd64.iso
/qemu/ISOs/questing/ubuntu-25.10-live-server-amd64.iso
⬆️⬆️⬆️⬆️⬆️⬆️⬆️⬆️
# Simplest usage - ubuntu-name is auto-derived from version
./run-packer.sh --ubuntu-version 24.04.2 --discenc NOENC --disk-size 5G
# Full example with all options (ubuntu-name optional)
./run-packer.sh \
--docker \
--discenc NOENC \
--ubuntu-version 24.04.2 \
--output-prefix /qemu/builds/ \
--disk-size 5G \
--disks 2 \
--raidlevel mirror \
--secureboot \
--iso-src file:///qemu/ISOs \
--set MYHOSTNAME=myserver \
--set POOLNAME=zroot
# In github actions workflow matrix (can omit ubuntu-name now)
./run-packer.sh \
--discenc "${{ matrix.discenc }}" \
--ubuntu-version "${{ matrix.ubuntu_version }}" \
--disk-size "${{ matrix.disk_size }}"
USAGE
}
# Set main qemu working dir
QEMU_ROOT="${QEMU_ROOT:-/qemu}"
DOCKER_RUN="${DOCKER_RUN:-}" # Run packer in container or not
CONFIG_FILE="${CONFIG_FILE:-ZFS-root.conf.packerci}" # Preseed config file for ZFS-root.sh
DISCENC="${DISCENC:-NOENC}" # Disk encryption
VER="${VER:-24.04.2}" # Ubuntu release to install
NAME="${NAME:-}" # Ubuntu release name
OUT_PREFIX="${OUT_PREFIX:-${QEMU_ROOT}/builds/}" # Output dir for packer artifacts
DISK_SIZE="${DISK_SIZE:-5G}" # Disk size
DISKS="${DISKS:-}" # Total number of disks if not 1
RAIDLEVEL="${RAIDLEVEL:-}" # Raid type for multi-disk (mirror, raidz1)
SECUREBOOT="${SECUREBOOT:-}" # Enable SecureBoot
ISO_SRC="${ISO_SRC:-}" # Location of bootable ISOs (eg. file///qemu/ISOs)
CONFIG_OVERRIDES=() # Array to collect --set KEY=VALUE pairs
while [[ $# -gt 0 ]]; do
case "$1" in
--docker) DOCKER_RUN=true ; shift ;;
--config) CONFIG_FILE="$2" ; shift 2 ;;
--discenc) DISCENC="$2"; shift 2 ;;
--ubuntu-version) VER="$2"; shift 2 ;;
--ubuntu-name) NAME="$2"; shift 2 ;;
--output-prefix) OUT_PREFIX="$2"; shift 2 ;;
--disk-size) DISK_SIZE="$2"; shift 2 ;;
--disks) DISKS="$2"; shift 2 ;;
--raidlevel) RAIDLEVEL="$2"; shift 2 ;;
--secureboot) SECUREBOOT="true"; shift ;;
--iso-src) ISO_SRC="$2"; shift 2 ;;
--set)
# Validate KEY=VALUE format
if [[ ! "$2" =~ ^[A-Z_][A-Z0-9_]*=.+$ ]]; then
echo "Error: --set requires KEY=VALUE format (e.g., MYHOSTNAME=myhost)" >&2
exit 1
fi
CONFIG_OVERRIDES+=("$2")
shift 2
;;
--help|-h) usage; exit 0 ;;
*) echo "Unknown option: $1" >&2; usage; exit 1 ;;
esac
done
if [[ ! -e "${CONFIG_FILE}" ]] ; then
echo "Preseed config file ${CONFIG_FILE} does not exist"
exit 1
fi
if [[ ! -d "${QEMU_ROOT}" ]] ; then
echo "Main qemu dir ${QEMU_ROOT} does not exist"
exit 1
fi
if [[ ! -d "${QEMU_ROOT}/builds" ]] ; then
echo "packer builds dir ${QEMU_ROOT}/builds does not exist"
exit 1
fi
# Auto-derive ubuntu_version_name if not provided
# NOTE: The Packer config also does this derivation, so this is mainly for
# validation and to provide better error messages at the script level
if [[ -z "$NAME" && -n "$VER" ]]; then
case "$VER" in
25.10* ) NAME="questing" ;;
25.04* ) NAME="plucky" ;;
24.04* ) NAME="noble" ;;
22.04* ) NAME="jammy" ;;
20.04* ) NAME="focal" ;;
18.04* ) NAME="bionic" ;;
*)
echo "Unknown ubuntu_version '$VER' — please set --ubuntu-name explicitly." >&2
exit 1
;;
esac
fi
# If --secureboot was specified, ensure ZFS-root.sh also enables SecureBoot
if [[ "${SECUREBOOT}" == "true" ]]; then
CONFIG_OVERRIDES+=("SECUREBOOT=y")
fi
packer_args=( -var-file=ZFS-root_local.vars.hcl )
add_var() {
local var_name="$1"
local val="$2"
if [[ -n "$val" ]]; then
packer_args+=( -var "${var_name}=${val}" )
fi
}
add_var "discenc" "$DISCENC"
add_var "ubuntu_version" "$VER"
add_var "ubuntu_version_name" "$NAME"
add_var "output_prefix" "$OUT_PREFIX"
add_var "disk_size" "$DISK_SIZE"
add_var "raidlevel" "$RAIDLEVEL"
add_var "secureboot" "$SECUREBOOT"
add_var "ubuntu_live_iso_src" "$ISO_SRC"
add_var "config_file" "$CONFIG_FILE"
# Build config_overrides map from --set parameters
if [[ ${#CONFIG_OVERRIDES[@]} -gt 0 ]]; then
# Build HCL map syntax: {"KEY1":"VALUE1","KEY2":"VALUE2"} (JSON-style for CLI compatibility)
# Note: Escaping differs for docker (sh -c) vs direct packer execution
overrides_map="{"
for i in "${!CONFIG_OVERRIDES[@]}"; do
# Split KEY=VALUE
key="${CONFIG_OVERRIDES[$i]%%=*}"
value="${CONFIG_OVERRIDES[$i]#*=}"
# Escape quotes in value
if [[ -n "${DOCKER_RUN}" ]]; then
# Docker needs extra escaping for sh -c processing
escaped_value="${value//\"/\\\\\\\"}"
quote="\\\""
else
# Direct execution needs normal escaping
escaped_value="${value//\"/\\\"}"
quote="\""
fi
# Add to map with quoted keys and values (using colon for JSON-style syntax)
if [[ $i -eq 0 ]]; then
overrides_map+="${quote}${key}${quote}:${quote}${escaped_value}${quote}"
else
overrides_map+=",${quote}${key}${quote}:${quote}${escaped_value}${quote}"
fi
done
overrides_map+="}"
packer_args+=( -var "config_overrides=${overrides_map}" )
echo "Config overrides: ${CONFIG_OVERRIDES[*]}"
fi
# Running in docker requires headless, but running direct/local we can
# view the install directly
if [[ ! -n "${DOCKER_RUN}" ]] ; then
add_var "headless" "false"
fi
check_disks() {
# Check if DISKS is set and validate it
if [[ -n "${DISKS}" ]]; then
# Verify DISKS is a number
if ! [[ "${DISKS}" =~ ^[0-9]+$ ]]; then
echo "Error: DISKS must be a positive integer, got: ${DISKS}" >&2
exit 1
fi
# Only process if DISKS > 1
if [[ "${DISKS}" -gt 1 ]]; then
# Check that DISK_SIZE is set
if [[ -z "${DISK_SIZE}" ]]; then
echo "Error: DISK_SIZE must be set when using multiple disks" >&2
exit 1
fi
# Calculate number of additional disks
additional_count=$((DISKS - 1))
# Build the array string with escaped quotes for sh -c
disk_array=""
for ((i=0; i<additional_count; i++)); do
if [[ $i -eq 0 ]]; then
disk_array="\\\"${DISK_SIZE}\\\""
else
disk_array="${disk_array},\\\"${DISK_SIZE}\\\""
fi
done
# Add to packer_args
packer_args+=( -var "additional_disks=[${disk_array}]" )
echo "Adding ${additional_count} additional disk(s) of size ${DISK_SIZE}"
fi
fi
}
packer_init_docker() {
# Install packer qemu plugin
echo "Init packer and download packer-qemu plugin"
docker run --rm -it \
--privileged --cap-add=ALL \
-v "$(pwd)":"${PWD}" -w "${PWD}" \
-v ${QEMU_ROOT}/packer.d:/root/.cache/packer.d \
-e PACKER_PLUGIN_PATH="/root/.cache/packer.d/plugins" \
--entrypoint /bin/sh \
hashicorp/packer:light -c "packer init ZFS-root_local.pkr.hcl"
}
packer_docker() {
# Run packer in a docker container
docker_args=( -v /usr/share/OVMF:/usr/share/OVMF )
# If ISO_SRC is not defined, then the packer config will default to pulling
# the iso from https://releases.ubuntu.com
if [[ -n "${ISO_SRC}" ]] && [[ "${ISO_SRC}" =~ "file:///" ]] ; then
ISO_DIR=${ISO_SRC#file://}
if [[ ! -d "${ISO_DIR}" ]] ; then
echo "ISO src dir ${ISO_DIR} does not exist"
exit 1
else
docker_args+=( -v ${ISO_DIR}:/qemu/ISOs )
fi
fi
echo "Docker args ${docker_args[*]}"
# Install the packer qemu plugin if necessary
if [ ! -d ${QEMU_ROOT}/packer.d/plugins/github.com/hashicorp/qemu ] ; then
packer_init_docker
fi
docker run --rm -it \
--privileged --cap-add=ALL \
-v "$(pwd)":"${PWD}" -w "${PWD}" \
${docker_args[*]} \
-v ${QEMU_ROOT}/packer.d:/root/.cache/packer.d \
-v ${QEMU_ROOT}/builds:/qemu/builds \
-e PACKER_PLUGIN_PATH="/root/.cache/packer.d/plugins" \
-e PACKER_LOG=1 \
--entrypoint /bin/sh \
hashicorp/packer:light -c " \
apk add --no-cache qemu-system-x86_64 qemu-img >/dev/null 2>&1 && \
packer build ${packer_args[*]} ZFS-root_local.pkr.hcl"
}
packer_init_direct() {
# Install packer qemu plugin
echo "Init packer and download packer-qemu plugin"
export PACKER_PLUGIN_PATH=${QEMU_ROOT}/packer.d/plugins
packer init ZFS-root_local.pkr.hcl
}
packer_direct() {
# Install the packer qemu plugin if necessary
if [ ! -d ${QEMU_ROOT}/packer.d/plugins/github.com/hashicorp/qemu ] ; then
packer_init_direct
fi
export PACKER_PLUGIN_PATH=${QEMU_ROOT}/packer.d/plugins
export PACKER_LOG=1
# Use fifo + tee for live output while capturing to file
trap 'rm -f /tmp/packer-pipe; kill $TEE_PID 2>/dev/null || true' EXIT
mkfifo /tmp/packer-pipe
tee /tmp/packer-output.log < /tmp/packer-pipe &
TEE_PID=$!
packer build ${packer_args[*]} ZFS-root_local.pkr.hcl > /tmp/packer-pipe
BUILD_EXIT=$?
wait $TEE_PID
# Extract output directory from packer log
OUTPUT_DIR=$(grep -o '/qemu/builds/packer-[^/]*' /tmp/packer-output.log | head -1)
if [ -n "$OUTPUT_DIR" ] && [ -d "$OUTPUT_DIR" ]; then
cp /tmp/packer-output.log "$OUTPUT_DIR/packer-output.log"
fi
}
check_disks
echo "Starting build with ${packer_args[*]}"
if [[ -n "${DOCKER_RUN}" ]] ; then
packer_docker
else
packer_direct
fi