]> git.ipfire.org Git - people/jschlag/ipfire-3.x-image.git/blob - generate_image.sh
Improve how we execute commands in chroot
[people/jschlag/ipfire-3.x-image.git] / generate_image.sh
1 #!/bin/bash
2 ###############################################################################
3 # IPFire.org - An Open Source Firewall Solution #
4 # Copyright (C) - IPFire Development Team <info@ipfire.org> #
5 ###############################################################################
6
7 #Path of the script
8
9 SCRIPT_PATH="$(dirname "$(readlink -f "$0")")"
10
11 # Constants
12
13 # Proper error codes
14 EXIT_OK=0
15 EXIT_ERROR=1
16 EXIT_CONF_ERROR=2
17 EXIT_NOT_SUPPORTED=3
18 EXIT_NOT_HANDLED=4
19 EXIT_COMMAND_NOT_FOUND=127
20 EXIT_ERROR_ASSERT=128
21
22 EXIT_TRUE=0
23 EXIT_FALSE=1
24 EXIT_UNKNOWN=2
25
26 TRUE=0
27 FALSE=1
28
29
30
31 # Functions
32
33 log() {
34 local level=${1}
35 local message=${2}
36 echo "[${level}] ${message}"
37 }
38
39 cmd() {
40 local cmd=$@
41 local ret
42
43 log DEBUG "Running command: ${cmd}"
44
45 ${cmd}
46 ret=$?
47
48 case "${ret}" in
49 ${EXIT_OK})
50 return ${EXIT_OK}
51 ;;
52 *)
53 log DEBUG "Returned with code '${ret}'"
54 return ${ret}
55 ;;
56 esac
57 }
58
59 cleanup() {
60 # Unmount image
61 umount ${IMAGE_MOUNT_DIR}
62
63 # Remove partition from the kernel table.
64 partx -d ${outlo}p1
65
66 # Remove loopback device
67 losetup -d ${outlo}
68
69 # Drop working dir
70 if [ -d "${WORKING_DIR}" ]; then
71 rm -dfR "${WORKING_DIR}"
72 fi
73 }
74
75 generate_image_filename() {
76 local distro=${1}
77 local version=${2}
78 local arch=${3}
79
80 echo "${distro}-${version}-${arch}"
81 }
82
83 check_for_pakfire() {
84 local return_value=0
85
86 # TODO
87 # Check that pakfire-server binary is available
88 # Check that pakfire binary is available
89
90 # Check that repo files are installed. (pakfire need to know which repos exist)
91 local repo_dir="/etc/pakfire/repos"
92
93 if [ ! -d "${repo_dir}" ]; then
94 log ERROR "Could not find respository directory ${repo_dir}"
95 return_value=1
96 fi
97
98 return ${return_value}
99 }
100
101 compress_image() {
102 local compression=${1}
103 local image_file=${2}
104 local level=${3}
105
106 log debug "Compressing ${image_file} with ${compression}"
107
108 case "${compression}" in
109 "xz")
110 # Check that the file does not exist yet
111 if [ -f "${image_file}.xz" ]; then
112 log ERROR "Failed to compress the image. The file already exists"
113 return ${EXIT_ERROR}
114 fi
115 cmd xz "-${level}" "${image_file}"
116 ;;
117 "zip")
118 # Check that the file does not exist yet
119 if [ -f "${image_file}.zip" ]; then
120 log ERROR "Failed to compress the image. The file already exists"
121 return ${EXIT_ERROR}
122
123 fi
124 cmd zip "-${level}" "${image_file}.zip" "${image_file}"
125 # Remove the file which we compressed+
126 rm -f "${image_file}"
127 ;;
128
129 esac
130 }
131
132 reset_root_password() {
133 local root_dir=${1}
134 # Backup passwd file
135 cp -avf ${root_dir}/etc/passwd ${root_dir}/etc/passwd.orig
136
137 # Drop root password.
138 sed -e "s/^\(root:\)[^:]*:/\1:/" ${root_dir}/etc/passwd.orig > ${root_dir}/etc/passwd
139
140 # Remove passwd backup file.
141 rm -rvf ${root_dir}/etc/passwd.orig
142 }
143
144 clone_git_repos() {
145 # Dir where the repos should be located
146 local dir=${1}
147 shift
148 local repos="$@"
149
150 local repo
151
152 mkdir -pv ${dir}
153
154 (
155 cd ${dir}
156 # Clone git repositories.
157 for repo in ${repos}; do
158 git clone ${repo}
159 done
160 )
161 }
162
163 # This function is used to publish the produced images
164
165 publish() {
166 local path=${1}
167 # The image we created usually a img. file
168 local image_base_file=${2}
169
170 local image_name_final="$(generate_image_filename "${DISTRO}" "${VERSION}" "${ARCH}")"
171
172 local image_type
173 local compression_type
174 local compression_level=1
175
176 if [[ ${IMAGE_RELEASE} -eq ${TRUE} ]]; then
177 compression_level=9
178 fi
179
180 # Do these steps for every image format we like to publish
181 for image_type in ${IMAGE_TYPES_PUBLISH}; do
182 # Get compressioon type
183 compression_type="$(get_compression_type ${image_type})"
184 # Convert images to the type specified in IMAGE_TYPES_PUBLISH
185 convert_image "${image_type}" "${image_base_file}" "${image_name_final}"
186
187 # compress image.
188 compress_image "${compression_type}" "${image_name_final}.${image_type}" ${compression_level}
189
190 # Move images to this path
191 mv -f "${image_name_final}.${image_type}.${compression_type}" ${path}
192
193 # point the latest links to these images
194 ln -s -f "${path}/${image_name_final}.${image_type}.${compression_type}" \
195 "${path}/$(generate_image_filename "${DISTRO}" "latest" "${ARCH}").${image_type}.${compression_type}"
196
197 done
198
199 }
200
201 convert_image() {
202 local type=${1}
203 local from=${2}
204 local to=${3}
205
206 if [[ ${type} = "img" ]]; then
207 # We do not need to convert the image here but we need to rename
208 mv -f ${from} ${to}.${type}
209 return $?
210 fi
211
212 if [[ ${type} = "qcow2" ]]; then
213 local command="qemu-img convert -c -O ${type} ${from} ${to}.${type}"
214 else
215 local command="qemu-img convert -O ${type} ${from} ${to}.${type}"
216 fi
217
218 cmd ${command}
219 }
220
221 get_compression_type() {
222 local image_type=${1}
223
224 case "${image_type}" in
225 "qcow2" | "img")
226 # These types can be used only under Unix so we use xz as compression
227 echo "xz"
228 ;;
229 "vmdk" | "vdi")
230 # These types can be also under Windows so we use zip as compression
231 echo "zip"
232 ;;
233 esac
234
235 }
236
237 check_for_free_space() {
238 local space=${1}
239 local path=${2}
240
241 # Space needs to passed in MB
242 # df returns bytes so we multiple by 1024
243 space=$(( ${space} * 1024 ))
244
245 log debug $(df --output=avail ${path} | tail -n 1)
246 log debug ${space}
247
248 if [ $(df --output=avail ${path} | tail -n 1) -lt ${space} ]; then
249 log error "Not enough free space available under ${path}"
250 log error "Free space is $(df -h -B MB --output=avail ${path} | tail -n 1) but we need at least $(( ${space} / 1024 / 1024 ))MB"
251 return ${EXIT_ERROR}
252 fi
253 }
254
255 parse_cmdline() {
256 while [ $# -gt 0 ]; do
257 case "${1}" in
258 "--release")
259 IMAGE_RELEASE=${TRUE}
260 ;;
261
262 *)
263 error "Invalid argument: ${1}"
264 return ${EXIT_CONF_ERROR}
265 ;;
266 esac
267 shift
268 done
269 }
270
271 #
272 # General settings
273 #
274
275 ARCH="x86_64"
276 DISTRO="ipfire3"
277 VERSION="$(date +"%Y%m%d")"
278 WORKING_DIR=$(mktemp -d /tmp/ipfire3_image.XXXXXXXX)
279
280 log DEBUG "Working dir is ${WORKING_DIR}"
281
282 #
283 # Image
284 #
285
286 IMAGE_BASE_FILE="$(mktemp -u ${WORKING_DIR}/image_base_file.XXXXXXX.img)"
287 IMAGE_SIZE="8100"
288 IMAGE_MOUNT_DIR="$(mktemp -d ${WORKING_DIR}/image.XXXXXXX)"
289
290 IMAGE_TYPES_PUBLISH="qcow2 vmdk vdi img"
291
292 # The used filesystem.
293 FILESYSTEM="xfs"
294
295 # Additional packages which should be installed.
296 PACKAGES="xfsprogs kernel openssh-server wget htop tmux"
297
298 # Use git network stack. ( When using the git network stack,
299 # development tools and git automatically will be installed.)
300 USE_GIT_NETWORK_STACK="True"
301
302 # List of packages which are required to build the network stack.
303 NETWORK_BUILD_DEPS="autoconf automake docbook-xsl libnl3-devel libxslt systemd-devel"
304
305 # Install development tools.
306 INSTALL_DEV_PACKAGES="True"
307
308 # List of development tools which should be installed.
309 DEVELOPMENT_PACKAGES="make gcc libtool git"
310
311
312 # Git repositories which also should be checked, out.
313 GIT_REPOS=""
314
315 #
316 # Stuff for the local repo
317 #
318
319 # Use local repository.
320 USE_LOCAL_REPO="True"
321
322 # Source path for the local repo packages.
323 LOCAL_REPO_SOURCE_PATH="/var/lib/pakfire/local/"
324
325 # Path were the local repo is created
326 LOCAL_REPO_DIR="$(mktemp -d ${WORKING_DIR}/ipfire-repo.XXXXXXX)"
327
328 # Config file for the local repo
329 LOCAL_REPO_FILE="/etc/pakfire/repos/local.repo"
330
331
332 #
333 # Scripts starts here
334 #
335
336 #Parse cmdline
337 parse_cmdline $@
338
339 # Check that pakfire is working
340 check_for_pakfire
341
342 if ! check_for_free_space 10000 "${WORKING_DIR}"; then
343 exit ${EXIT_ERROR}
344 fi
345
346 # Check that the image does not exist yet
347 if [ -f ${IMAGE_BASE_FILE} ]; then
348 log ERROR "Image file does already exists"
349 exit 1
350 fi
351
352 # Check that the local repo file does not exists yet.
353 # We do not want to override custom user configurations.
354 if [ -f "${LOCAL_REPO_FILE}" ]; then
355 log ERROR "Config file ${LOCAL_REPO_FILE} for the local repo does already exists"
356 exit 1
357 fi
358
359 # cd into working directory
360 cd ${WORKING_DIR} || exit ${EXIT_ERROR}
361
362 #
363 ## Create the disk image.
364 #
365 dd if=/dev/zero of=${IMAGE_BASE_FILE} seek=${IMAGE_SIZE}M count=1k bs=1
366
367 # Setup the loopback device.
368 outlo=`losetup -f --show ${IMAGE_BASE_FILE}`
369
370 log INFO "Create partions and filesystem"
371
372 # Create and msdos compatible table on the image.
373 parted ${outlo} mklabel msdos
374
375 # Add a new partition to the image.
376 parted ${outlo} mkpart primary ${FILESYSTEM} 2048k 100% -a minimal
377
378 # Make the primary partition bootable.
379 parted ${outlo} set 1 boot on
380
381 # Notify the kernel about the new partition.
382 partx -a ${outlo}
383
384 #
385 ## Create the filesystem.
386 #
387 mkfs.${FILESYSTEM} ${outlo}p1
388
389 #
390 ## Mount the filesystem.
391 #
392
393 log INFO "Mount partion in ${IMAGE_MOUNT_DIR}"
394
395 # Afterwards mount the image.
396 mount -t ${FILESYSTEM} ${outlo}p1 ${IMAGE_MOUNT_DIR}
397
398 #
399 ## Install IPFire 3.x.
400 #
401
402 # Add grub on x86_64 to the package list.
403 if [ "${ARCH}" == "x86_64" ] || [ "${ARCH}" == "i686" ]; then
404 PACKAGES="${PACKAGES} grub"
405
406 # Store, that grub is present.
407 HAVE_GRUB="True"
408 fi
409
410 # Check if the git network stack should be installed.
411 if [ "${USE_GIT_NETWORK_STACK}" == "True" ]; then
412 GIT_REPOS="${GIT_REPOS} git://git.ipfire.org/network.git"
413
414 # Add build dependencies of network package.
415 PACKAGES="${PACKAGES} ${NETWORK_BUILD_DEPS}"
416 fi
417
418 # Add develoment packes to the package list, if required.
419 if [ "${INSTALL_DEV_PACKAGES}" == "True" ] || [ ! -z "${GIT_REPOS}" ]; then
420 PACKAGES="${PACKAGES} ${DEVELOPMENT_PACKAGES}"
421 fi
422
423 log INFO "Create local respository"
424
425
426 # Check if the local repo should be used.
427 if [ "${USE_LOCAL_REPO}" == "True" ]; then
428 # Create local repository.
429 mkdir -pv "${LOCAL_REPO_DIR}"
430
431 # Master repository.
432 if ! pakfire-server repo create ${LOCAL_REPO_DIR} ${LOCAL_REPO_SOURCE_PATH}; then
433 log ERROR "Failed to create a local respository"
434 cleanup
435 exit 1
436 fi
437
438 # Create temporary pakfire repo file.
439 echo "[repo:local]" >> "${LOCAL_REPO_FILE}"
440 echo "description = Local repository." >> "${LOCAL_REPO_FILE}"
441 echo "enabled = 0" >> "${LOCAL_REPO_FILE}"
442 echo "baseurl = ${LOCAL_REPO_DIR}" >> "${LOCAL_REPO_FILE}"
443
444 ENABLE_LOCAL="--enable-repo=local"
445 fi
446
447 # Install IPFire 3.x in the created image.
448 yes | pakfire --root=${IMAGE_MOUNT_DIR} ${ENABLE_LOCAL} install @Base ${PACKAGES}
449
450 #
451 # Enable serial console
452 #
453
454
455 #echo "GRUB_TERMINAL=\"serial console\"" >> "${IMAGE_MOUNT_DIR}/etc/default/grub"
456 #echo "GRUB_SERIAL_COMMAND=\"serial --unit=0 --speed=115200\"" >> "${IMAGE_MOUNT_DIR}/etc/default/grub"
457
458 #Hack to install a /etc/default/grub file
459
460 cmd cp -f "${SCRIPT_PATH}/grub" "${IMAGE_MOUNT_DIR}/etc/default"
461
462 #
463 ## Install grub2 if neccessary
464 #
465 if [ "${HAVE_GRUB}" == "True" ]; then
466 grub2-install --boot-directory=${IMAGE_MOUNT_DIR}/boot/ --modules="${FILESYSTEM} part_msdos" ${outlo}
467 fi
468
469 #
470 ## Generate fstab
471 #
472
473 # Gather the uuid of the partition.
474 FS_UUID=$(blkid -o value -s UUID ${outlo}p1)
475
476 # Write fstab.
477 echo "UUID=${FS_UUID} / ${FILESYSTEM} defaults 0 0" > ${IMAGE_MOUNT_DIR}/etc/fstab
478
479 #
480 ## Remove the password for user root.
481 #
482
483 reset_root_password "${IMAGE_MOUNT_DIR}"
484
485 #
486 ## Setup git repositories.
487 #
488
489 clone_git_repos "${IMAGE_MOUNT_DIR}/build" ${GIT_REPOS}
490
491 #
492 ## Prepare chrooting into the image.
493 #
494
495 # Check if the network stack should be build.
496 if [ "${USE_GIT_NETWORK_STACK}" == "True" ]; then
497 BUILD_NETWORK_CMDS="cd network/ && ./autogen.sh && ./configure && make && make install"
498 fi
499
500 ENABLE_GETTY="/bin/systemctl enable getty@.service"
501
502 # Check if the arch uses grub
503 if [ "${HAVE_GRUB}" == "True" ]; then
504 GENERATE_GRUB_CONF="grub-mkconfig -o /boot/grub2/grub.cfg"
505 fi
506
507 # Use systemd-nspawn to spawn a chroot environment and execute
508 # commands inside it.
509 #
510 # The first command enables the terminal on TTY1.
511 # The second command generates the configuration file for grub2.
512
513
514 systemd-nspawn -D ${IMAGE_MOUNT_DIR} --bind /dev --capability=CAP_SYS_ADMIN,CAP_SYS_RAWIO --bind /proc --bind /sys << END
515 echo "Execute commands inside chroot"
516 ${ENABLE_GETTY}
517 ${GENERATE_GRUB_CONF}
518 cd /build/ && ls
519 ${BUILD_NETWORK_CMDS}
520 echo "All commands executed"
521 END
522
523
524
525 # Insert the UUID because grub-mkconfig often fails to
526 # detect that correctly
527
528 sed -i "${IMAGE_MOUNT_DIR}/boot/grub2/grub.cfg" \
529 -e "s/root=[A-Za-z0-9\/=-]*/root=UUID=${FS_UUID}/g"
530
531 cat "${IMAGE_MOUNT_DIR}/boot/grub2/grub.cfg"
532 #
533 ## Tidy up.
534 #
535
536 # Wait a second.
537 sleep 5
538
539 # Check filesystem for damage.
540 fsck.${FILESYSTEM} ${outlo}p1
541
542 publish "/home/jschlag/public/ipfire3-images" "${IMAGE_BASE_FILE}"
543
544 # Cleanup
545 cleanup