├── .gitignore ├── .gitlab-ci.yml ├── Makefile ├── Makefile.builder ├── README ├── appmenus_generic ├── netvm-whitelisted-appmenus.list ├── vm-whitelisted-appmenus.list └── whitelisted-appmenus.list ├── appvm.buildlog ├── build_template_rpm ├── builder_fix_filenames ├── builder_setup ├── cleanup_image ├── create_template_list.sh ├── functions-name.sh ├── functions.sh ├── mount_root.sh ├── prepare_image ├── prepared_images └── .gitignore ├── qubeize_image ├── qubeized_images └── .gitignore ├── rpm └── .gitignore ├── rpmbuild └── .gitignore ├── template_generic.conf ├── templates.spec ├── tests ├── assert │ ├── .travis.yml │ ├── COPYING │ ├── COPYING.LESSER │ ├── README.rst │ ├── assert.sh │ └── tests.sh ├── shellcheck │ └── INSTALL └── template-flavors │ ├── NOTES │ ├── another_location │ ├── whonix gw │ │ ├── packages.list │ │ └── test_pre.sh │ ├── whonix-gw │ │ └── test_pre.sh │ └── whonix_gnome │ │ ├── packages.list │ │ └── test_pre.sh │ ├── debian+whonix-gateway+gnome │ └── test_pre.sh │ ├── debian+whonix-gateway │ └── test_pre.sh │ ├── packages.list │ ├── packages_wheezy.list │ ├── proxy │ └── test_pre.sh │ ├── test.sh │ ├── test_copy_location │ ├── test1 │ ├── test2 │ └── test3 │ ├── test_pre.sh │ ├── wheezy+whonix-gateway+gnome │ ├── packages.list │ └── test_pre.sh │ ├── wheezy+whonix-gateway │ ├── files │ │ ├── test1 │ │ ├── test2 │ │ └── test3 │ ├── packages.list │ └── test_pre.sh │ ├── wheezy │ └── test_pre.sh │ ├── whonix-gateway+gnome │ └── test_pre.sh │ └── whonix-gateway │ └── test_pre.sh ├── umount-kill ├── umount_kill.sh └── version /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | appmenus 3 | template.conf 4 | build_timestamp_* 5 | *.iso 6 | *.img 7 | install-templates.sh 8 | pkgs-for-template/* 9 | cache_*/ 10 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - project: 'QubesOS/qubes-continuous-integration' 3 | file: '/r4.1/gitlab-linux-template-builder.yml' 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifndef DIST 2 | $(error "You must set DIST variable, e.g. DIST=fc14") 3 | endif 4 | export DIST 5 | 6 | TEMPLATE_ENV_WHITELIST ?= 7 | TEMPLATE_BUILDER = 1 8 | -include $(addsuffix /Makefile.builder,$(BUILDER_PLUGINS_DIRS)) 9 | 10 | TEMPLATE_NAME := $(DIST) 11 | ifdef TEMPLATE_FLAVOR 12 | TEMPLATE_NAME := $(TEMPLATE_NAME)-$(TEMPLATE_FLAVOR) 13 | endif 14 | 15 | # Support for builderv2 16 | IS_LEGACY_BUILDER=1 17 | 18 | # expose those variables to template-building scripts 19 | TEMPLATE_ENV_WHITELIST += \ 20 | DIST DISTRIBUTION TEMPLATE_SCRIPTS TEMPLATE_NAME TEMPLATE_FLAVOR \ 21 | TEMPLATE_FLAVOR_DIR TEMPLATE_OPTIONS APPMENUS_DIR \ 22 | VERBOSE DEBUG PATH BUILDER_DIR SRC_DIR DISCARD_PREPARED_IMAGE \ 23 | TEMPLATE_ROOT_WITH_PARTITIONS TEMPLATE_ROOT_SIZE \ 24 | USE_QUBES_REPO_VERSION USE_QUBES_REPO_TESTING \ 25 | BUILDER_TURBO_MODE REPO_PROXY FEDORA_MIRROR \ 26 | CENTOS_MIRROR EPEL_MIRROR QUBES_MIRROR \ 27 | GENTOO_MIRROR ARCHLINUX_MIRROR \ 28 | DIST_DOM0 RELEASE IS_LEGACY_BUILDER 29 | 30 | # Make sure names are < 32 characters, process aliases 31 | fix_up := $(shell TEMPLATE_NAME=$(TEMPLATE_NAME) \ 32 | TEMPLATE_LABEL="$(TEMPLATE_LABEL)" \ 33 | ./builder_fix_filenames) 34 | TEMPLATE_NAME := $(word 1,$(fix_up)) 35 | 36 | export TEMPLATE_NAME 37 | export TEMPLATE_SCRIPTS 38 | export DISTRIBUTION 39 | export IS_LEGACY_BUILDER 40 | 41 | VERSION := $(shell cat version) 42 | TEMPLATE_TIMESTAMP ?= $(shell date -u +%Y%m%d%H%M) 43 | 44 | .PHONY: help template-name prepare package rpms rootimg-build 45 | .PHONY: update-repo-templates-itl update-repo-templates-community 46 | 47 | help: 48 | @echo "make rpms -- generate template rpm" 49 | @echo "make update-repo-installer -- copy newly generated rpm to installer repo" 50 | @echo "make clean -- remove all files and directories built or added" 51 | 52 | template-name: 53 | @echo $(TEMPLATE_NAME) 54 | 55 | prepare: 56 | @echo "Building template: $(TEMPLATE_NAME)" 57 | @echo $(TEMPLATE_TIMESTAMP) > build_timestamp_$(TEMPLATE_NAME) 58 | 59 | package: 60 | ./build_template_rpm $(TEMPLATE_NAME) 61 | 62 | rpms: prepare rootimg-build package 63 | ./create_template_list.sh || : 64 | 65 | rootimg-build: 66 | ifeq (,$(TEMPLATE_SCRIPTS)) 67 | $(error Building template $(DIST) not supported by any of configured plugins) 68 | endif 69 | sudo env -i $(foreach var,$(TEMPLATE_ENV_WHITELIST),$(var)="$($(var))") \ 70 | ./prepare_image prepared_images/$(TEMPLATE_NAME).img 71 | sudo env -i $(foreach var,$(TEMPLATE_ENV_WHITELIST),$(var)="$($(var))") \ 72 | ./qubeize_image prepared_images/$(TEMPLATE_NAME).img $(TEMPLATE_NAME) 73 | 74 | update-repo-templates-itl: update-repo-templates.itl 75 | update-repo-templates-community: update-repo-templates.community 76 | 77 | update-repo-templates.%: repo = $(subst .,,$(suffix $@)) 78 | update-repo-templates.%: 79 | [ -z "$$UPDATE_REPO" ] && UPDATE_REPO=../linux-yum/current-release/templates-$(repo);\ 80 | ln -f rpm/noarch/qubes-template-$(TEMPLATE_NAME)-$(VERSION)-$(shell cat build_timestamp_$(TEMPLATE_NAME))*.noarch.rpm $$UPDATE_REPO/rpm 81 | 82 | update-repo-installer: 83 | [ -z "$$UPDATE_REPO" ] && UPDATE_REPO=../installer/yum/qubes-dom0;\ 84 | ln -f rpm/noarch/qubes-template-$(TEMPLATE_NAME)-$(VERSION)-$(shell cat build_timestamp_$(TEMPLATE_NAME))*.noarch.rpm $$UPDATE_REPO/rpm 85 | 86 | sign: 87 | setsid -w rpmsign $$RPMSIGN_OPTS --addsign rpm/noarch/qubes-template-$(TEMPLATE_NAME)-$(VERSION)-$(shell cat build_timestamp_$(TEMPLATE_NAME))*.noarch.rpm 88 | 89 | prepare-repo-template: 90 | rm -rf pkgs-for-template/$(DIST) 91 | mkdir -p pkgs-for-template/$(DIST) 92 | 93 | clean: 94 | sudo rm -fr qubeized_images/root.img.* 95 | sudo rm -fr qubeized_images/$(TEMPLATE_NAME)* 96 | sudo rm -fr rpmbuild/BUILDROOT/* 97 | sudo rm -fr rpmbuild/tmp/* 98 | # We're not removing any images from prepared_images/ intentionally 99 | # because the user might want to keep using the same image for a long time 100 | # and they are not dependent on any of the Qubes packages 101 | 102 | -------------------------------------------------------------------------------- /Makefile.builder: -------------------------------------------------------------------------------- 1 | # pretend that normal package is built from this repo, to reuse update-repo-* 2 | ifeq ($(PACKAGE_SET),vm) 3 | OUTPUT_DIR = rpm 4 | RPM_SPEC_FILES = templates.spec 5 | endif 6 | 7 | NO_ARCHIVE := 1 8 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | The Template Builder 2 | ====================== 3 | 4 | 1) First, create a clean image of Fedora Linux install. You can use the fedoraize_image 5 | script for this: 6 | 7 | fedorize_image 8 | 9 | NOTE: The fedorize_image script can be also used to update an image -- it 10 | automatically assumes the update mode if the already exists. 11 | 12 | 2) Update symlinks in rpms_to_install/ using the create_symlinks_in_rpms_to_install_dir.sh script. The script 13 | reads the core, gui, and xen packages versions from version_{core,gui,xen} files and creates appropriate symlinks 14 | in rpms_to_install/ dir. 15 | 16 | Verify that all symlinks are correct. 17 | 18 | 3) Now, you can "qubeize" the image. This must be done as root. E.g.: 19 | 20 | # ./qubeize_image 21 | 22 | 4) Finally, build the Template RPM (do it as normal user, not root), e.g.: 23 | 24 | $./build_rpm 25 | 26 | The resulting rpm is stored in rpm/noarch directory. 27 | 28 | -------------------------------------------------------------------------------- /appmenus_generic/netvm-whitelisted-appmenus.list: -------------------------------------------------------------------------------- 1 | gnome-terminal.desktop 2 | -------------------------------------------------------------------------------- /appmenus_generic/vm-whitelisted-appmenus.list: -------------------------------------------------------------------------------- 1 | gnome-terminal.desktop 2 | nautilus.desktop 3 | mozilla-firefox.desktop 4 | mozilla-thunderbird.desktop 5 | openoffice.org-startcenter.desktop 6 | -------------------------------------------------------------------------------- /appmenus_generic/whitelisted-appmenus.list: -------------------------------------------------------------------------------- 1 | gnome-terminal.desktop 2 | gpk-application.desktop 3 | gpk-install-catalog.desktop 4 | gpk-install-file.desktop 5 | gpk-log.desktop 6 | gpk-prefs.desktop 7 | gpk-repo.desktop 8 | system-config-date.desktop 9 | system-config-printer.desktop 10 | -------------------------------------------------------------------------------- /appvm.buildlog: -------------------------------------------------------------------------------- 1 | $ su 2 | 3 | # ./fedorize_image fedorized_images/f13-x64-root.img clean_images/packages-appvm.list 4 | 5 | # ./qubeize_image_appvm fedorized_images/f13-x64-root.img linux-x64 6 | 7 | # kpartx -a qubeized_images/linux-x64-root.img 8 | # mount /dev/mapper/loop0p1 mnt/ 9 | # cp mnt/boot/vmlinuz-2.6.32.14-1.2.105a.pvops0.qubes.x86_64 mnt/boot/initramfs-2.6.32.14-1.2.105a.pvops0.qubes.x86_64.img vm_kernels_appvm/ 10 | # umount mnt/ 11 | # kpartx -d qubeized_images/linux-x64-root.img 12 | # 13 | # cd vm_kernels_appvm 14 | # ../../core/dom0/aux-tools/patch_appvm_initramfs.sh initramfs-2.6.32.14-1.2.105a.pvops0.qubes.x86_64.img initramfs-2.6.32.14-1.2.105a.pvops0.qubes.x86_64.qubeized.img `pwd`/../vm_initramfs_patches/qubes_cow_setup.sh 15 | # ln -sf initramfs-2.6.32.14-1.2.105a.pvops0.qubes.x86_64.qubeized.img initramfs 16 | # ln -sf vmlinuz-2.6.32.14-1.2.105a.pvops0.qubes.x86_64 vmlinuz 17 | 18 | # exit 19 | 20 | $ ./build_template_rpm linux-x64 21 | -------------------------------------------------------------------------------- /build_template_rpm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | NAME=$1 3 | 4 | set -e 5 | if [ "${VERBOSE:-0}" -ge 2 ] || [ "${DEBUG:-0}" -eq 1 ]; then 6 | set -x 7 | fi 8 | 9 | if [ -z "$NAME" ] ; then 10 | echo "usage $0 " 11 | exit 1 12 | fi 13 | 14 | trap "rm -rf appmenus-$$ appmenus template.conf" EXIT 15 | 16 | # handle appmenus whitelists from DispVM build 17 | if [ -d "qubeized_images/$NAME/untrusted_appmenus" ]; then 18 | srcdir="qubeized_images/$NAME/untrusted_appmenus" 19 | mkdir -p appmenus-$$ 20 | rm -f appmenus 21 | ln -s appmenus-$$ appmenus 22 | for f in whitelisted-appmenus.list vm-whitelisted-appmenus.list netvm-whitelisted-appmenus.list; do 23 | grep '^[a-zA-Z0-9.()_-]\+.desktop$' "$srcdir/$f" > appmenus/$f 24 | done 25 | fi 26 | 27 | # handle template.conf from DispVM build 28 | if [ -r "qubeized_images/$NAME/template.conf" ]; then 29 | rm -f template.conf 30 | ln -s qubeized_images/$NAME/template.conf template.conf 31 | fi 32 | 33 | # Default compression format for binary payloads 34 | # It applies for Fedora < 31 35 | COMPRESSION=w2.xzdio 36 | if [ -n "$DIST_DOM0" ]; then 37 | if [ "${DIST_DOM0#fc}" != "${DIST_DOM0}" ]; then 38 | DIST_VER="${DIST_DOM0#fc}" 39 | fi 40 | 41 | if [ "${DIST_VER}" -ge 31 ]; then 42 | COMPRESSION=w19.zstdio 43 | fi 44 | fi 45 | 46 | rpmbuild --target noarch \ 47 | --define "template_name $NAME" \ 48 | --define "DIST $DIST" \ 49 | --define "_topdir $PWD/rpmbuild" \ 50 | --define "_tmppath $PWD/rpmbuild/tmp" \ 51 | --define "_source_payload $COMPRESSION" \ 52 | --define "_binary_payload $COMPRESSION" \ 53 | -bb templates.spec 54 | 55 | if [ "0$DISCARD_PREPARED_IMAGE" -eq "1" ]; then 56 | rm -rf "qubeized_images/$NAME" 57 | fi 58 | -------------------------------------------------------------------------------- /builder_fix_filenames: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check template name length and fix if not under 32 characters 4 | # Return the result 5 | 6 | . ./functions-name.sh > /dev/null 7 | 8 | # Check for custom template name 9 | templateNameDist "${TEMPLATE_NAME}" 10 | -------------------------------------------------------------------------------- /builder_setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Setup env variables 4 | 5 | SCRIPTSDIR=$TEMPLATE_SCRIPTS 6 | if [ ! -d "$SCRIPTSDIR" ]; then 7 | echo "Scripts directory $SCRIPTSDIR does not exists" 8 | exit 1 9 | fi 10 | 11 | # Support for builderv2 12 | TEMPLATE_CONTENT_DIR="${SCRIPTSDIR}" 13 | IS_LEGACY_BUILDER=1 14 | 15 | if [ -z "$CACHEDIR" ]; then 16 | CACHEDIR=$PWD/cache_$DIST 17 | fi 18 | 19 | export SCRIPTSDIR CACHEDIR TEMPLATE_CONTENT_DIR IS_LEGACY_BUILDER 20 | -------------------------------------------------------------------------------- /cleanup_image: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export INSTALLDIR=$1 4 | 5 | . ./builder_setup 6 | 7 | set -e 8 | 9 | if ! [ $# -eq 1 ]; then 10 | echo "usage $0 " 11 | exit 1 12 | fi 13 | 14 | 15 | if ! [ -d $INSTALLDIR ]; then 16 | echo $INSTALLDIR does not exist 17 | exit 1 18 | fi 19 | 20 | echo "--> Cleaning up image file..." 21 | $SCRIPTSDIR/09_cleanup.sh 22 | 23 | echo "--> Compacting image file..." 24 | /sbin/fstrim -v "$INSTALLDIR" 25 | -------------------------------------------------------------------------------- /create_template_list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # vim: set ts=4 sw=4 sts=4 et : 3 | 4 | # 5 | # Creates a small script to copy to dom0 to retrieve the generated template rpm's 6 | # 7 | 8 | set -e 9 | 10 | template_dir="$(readlink -m ./rpm/install-templates.sh)" 11 | path="$(readlink -m .)/rpm/noarch" 12 | version="-$(cat ./version)" 13 | name="$(xenstore-read name)" 14 | 15 | files_list_temp="$(echo "rpm/noarch/"*)" 16 | files_list_temp="$(printf "%s \n" ${files_list_temp[@]})" 17 | ## Newest versions first. 18 | files_list_temp="$(echo "$files_list_temp" | sort --reverse)" 19 | 20 | for file_name in $files_list_temp ; do 21 | file_name_without_version="$(echo "${file_name}" | sed -r "s/(${version}).+$//")" 22 | template_name="$(basename "$file_name_without_version")" 23 | template_list+="$template_name " 24 | done 25 | 26 | template_list="$(printf "%s \n" ${template_list[@]})" 27 | template_list="$(echo "$template_list" | sort --unique)" 28 | echo "template_list: $template_list" 29 | 30 | declare -A -g remembered 31 | 32 | for template_item_from_template_list in $template_list ; do 33 | for file_name in $files_list_temp ; do 34 | file_name_without_version="$(echo "${file_name}" | sed -r "s/(${version}).+$//")" 35 | template_name="$(basename "$file_name_without_version")" 36 | file_name_basename="$(basename "$file_name")" 37 | if [ ! "$template_item_from_template_list" = "$template_name" ]; then 38 | continue 39 | fi 40 | if [ "${remembered["$template_name"]}" = "true" ]; then 41 | files+="#$file_name_basename " 42 | else 43 | remembered["$template_name"]="true" 44 | files+="$file_name_basename " 45 | fi 46 | done 47 | done 48 | 49 | files=" 50 | $(printf "%s \n" ${files[@]}) 51 | " 52 | 53 | # ----------------------------------------------------------------------------- 54 | # Write $vars 55 | # ----------------------------------------------------------------------------- 56 | cat << EOF > "${template_dir}" 57 | #!/bin/bash 58 | 59 | # Use the following command in DOM0 to retreive this file: 60 | # qvm-run --pass-io ${name} 'cat ${template_dir}' > install-templates.sh 61 | 62 | files="${files}" 63 | 64 | path="${path}" 65 | version="${version}" 66 | name="${name}" 67 | EOF 68 | 69 | # ----------------------------------------------------------------------------- 70 | # Write installation function 71 | # ----------------------------------------------------------------------------- 72 | cat << 'EOF' >> "${template_dir}" 73 | 74 | for file_name in ${files[@]}; do 75 | if echo "$file_name" | grep -q '^#' ; then 76 | continue 77 | fi 78 | 79 | if [ ! -e "${file_name}" ]; then 80 | echo "Copying ${file_name} from ${name} to ${PWD}/${file_name}..." 81 | qvm-run --pass-io "${name}" "cat ${path}/${file_name}" > "${PWD}/${file_name}" 82 | fi 83 | 84 | package_name="$(echo "${file_name}" | sed -r "s/(${version}).+$//")" 85 | 86 | if sudo yum $YUM_OPTS list installed "$package_name" >/dev/null 2>&1 ; then 87 | echo "Uninstalling package ${package_name}..." 88 | sudo yum $YUM_OPTS erase "$package_name" 89 | fi 90 | 91 | echo "Installing file ${file_name}..." 92 | if sudo yum $YUM_OPTS install "${file_name}" ; then 93 | echo "Deleting ${PWD}/${file_name}..." 94 | rm -f "${file_name}" 95 | fi 96 | done 97 | 98 | echo "Done." 99 | EOF 100 | 101 | # ----------------------------------------------------------------------------- 102 | # Display instructions 103 | # ----------------------------------------------------------------------------- 104 | echo "Use the following command in DOM0 to retreive this file:" 105 | echo "qvm-run --pass-io ${name} 'cat ${template_dir}' > install-templates.sh" 106 | -------------------------------------------------------------------------------- /functions-name.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # vim: set ts=4 sw=4 sts=4 et : 3 | 4 | set -e 5 | 6 | VERBOSE=${VERBOSE:-1} 7 | DEBUG=${DEBUG:-0} 8 | 9 | containsFlavor() { 10 | flavor="${1}" 11 | retval=1 12 | local template_options 13 | 14 | # shellcheck disable=SC2153 15 | read -r -a template_options <<<"${TEMPLATE_OPTIONS[@]}" 16 | 17 | # Check the template flavor first 18 | if [ "${flavor}" == "${TEMPLATE_FLAVOR}" ]; then 19 | retval=0 20 | fi 21 | 22 | # Check the template flavors next 23 | elementIn "${flavor}" "${template_options[@]}" && { 24 | retval=0 25 | } 26 | 27 | return ${retval} 28 | } 29 | 30 | templateFlavorPrefix() { 31 | local template_flavor=${1-${TEMPLATE_FLAVOR}} 32 | local template_flavor_prefix 33 | # shellcheck disable=SC2153 34 | read -r -a template_flavor_prefix <<<"${TEMPLATE_FLAVOR_PREFIX[@]}" 35 | 36 | for element in "${template_flavor_prefix[@]}" 37 | do 38 | if [ "${element%:*}" == "${DIST}+${template_flavor}" ]; then 39 | echo "${element#*:}" 40 | return 41 | fi 42 | done 43 | 44 | # If template_flavor only contains a '+'; send back $DIST 45 | if [ "${template_flavor}" == "+" ]; then 46 | echo "${DIST}" 47 | else 48 | echo "${DIST}${template_flavor:++}" 49 | fi 50 | } 51 | 52 | templateNameFixLength() { 53 | local template_name="${1}" 54 | local temp_name 55 | read -r -a temp_name <<<"${template_name//+/ }" 56 | local index=$(( ${#temp_name[@]}-1 )) 57 | 58 | while [ ${#template_name} -ge 32 ]; do 59 | template_name=$(printf '%s' "${temp_name[0]}") 60 | if [ $index -gt 0 ]; then 61 | template_name+=$(printf '+%s' "${temp_name[@]:1:index}") 62 | fi 63 | (( index-- )) 64 | if [ $index -lt 1 ]; then 65 | template_name="${template_name:0:31}" 66 | fi 67 | done 68 | 69 | echo "${template_name}" 70 | } 71 | 72 | templateNameDist() { 73 | local dist_name="${1}" 74 | template_name="$(templateName)" && dist_name="${template_name}" 75 | 76 | # Automaticly correct name length if it's greater than 32 chars 77 | dist_name="$(templateNameFixLength "${dist_name}")" 78 | 79 | # Remove and '+' characters from name since they are invalid for name 80 | dist_name="${dist_name//+/-}" 81 | echo "${dist_name}" 82 | } 83 | 84 | templateName() { 85 | local template_flavor=${1:-${TEMPLATE_FLAVOR}} 86 | local template_name 87 | local template_options 88 | local template_label 89 | local template_options_concatenated 90 | retval=1 # Default is 1; mean no replace happened 91 | 92 | read -r -a template_options <<< "${TEMPLATE_OPTIONS[@]}" 93 | 94 | # Only apply options if $1 was not passed 95 | if [ -n "${1}" ] || [ -z "${TEMPLATE_OPTIONS[*]}" ]; then 96 | template_options_concatenated= 97 | else 98 | template_options_concatenated=$(printf '+%s' "${template_options[@]}") 99 | fi 100 | 101 | template_name="$(templateFlavorPrefix "${template_flavor}")${template_flavor}${template_options_concatenated}" 102 | 103 | # shellcheck disable=SC2153 104 | read -r -a template_label <<<"${TEMPLATE_LABEL[@]}" 105 | 106 | for element in "${template_label[@]}"; do 107 | if [ "${element%:*}" == "${template_name}" ]; then 108 | template_name="${element#*:}" 109 | retval=0 110 | break 111 | fi 112 | done 113 | 114 | # shellcheck disable=SC2005 115 | echo "$(templateNameFixLength "${template_name}")" 116 | return $retval 117 | } 118 | -------------------------------------------------------------------------------- /functions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # vim: set ts=4 sw=4 sts=4 et : 3 | 4 | set -e 5 | 6 | VERBOSE=${VERBOSE:-1} 7 | DEBUG=${DEBUG:-0} 8 | 9 | . ./functions-name.sh >/dev/null 10 | 11 | # ------------------------------------------------------------------------------ 12 | # Run a command inside chroot 13 | # ------------------------------------------------------------------------------ 14 | if [ "0${VERBOSE}" -ge 2 ] || [ "${DEBUG}" == "1" ]; then 15 | chroot_cmd() { 16 | local retval 17 | # Need to capture exit code after running chroot or systemd-nspawn 18 | # so it will be available as a return value 19 | # shellcheck disable=SC2015 20 | if [ "${SYSTEMD_NSPAWN_ENABLE}" == "1" ]; then 21 | systemd-nspawn -D "${INSTALL_DIR}" -M "${DIST}" ${1+"$@"} && { retval=$?; true; } || { retval=$?; true; } 22 | else 23 | /usr/sbin/chroot "${INSTALL_DIR}" ${1+"$@"} && { retval=$?; true; } || { retval=$?; true; } 24 | fi 25 | return $retval 26 | } 27 | else 28 | chroot_cmd() { 29 | if [ "${SYSTEMD_NSPAWN_ENABLE}" == "1" ]; then 30 | systemd-nspawn -D "${INSTALL_DIR}" -M "${DIST}" ${1+"$@"} 31 | else 32 | /usr/sbin/chroot "${INSTALL_DIR}" ${1+"$@"} 33 | fi 34 | } 35 | fi 36 | 37 | # ------------------------------------------------------------------------------ 38 | # Display messages 39 | # ------------------------------------------------------------------------------ 40 | # Only output text under certain conditions 41 | output() { 42 | if [ "0${VERBOSE}" -ge 1 ] && [[ -z ${TEST} ]]; then 43 | # Don't echo if -x is set since it will already be displayed via true 44 | [[ ${-/x} != "$-" ]] || echo -e "$@" 45 | fi 46 | } 47 | 48 | outputc() { 49 | shift 50 | output "$*" || : 51 | } 52 | 53 | info() { 54 | output "INFO: $*" || : 55 | } 56 | 57 | debug() { 58 | output "DEBUG: $*" || : 59 | } 60 | 61 | warn() { 62 | output "WARNING: $*" || : 63 | } 64 | 65 | error() { 66 | output "ERROR: $*" || : 67 | } 68 | 69 | # ------------------------------------------------------------------------------ 70 | # Return file or directory for current flavor. 71 | # 72 | # Example: 73 | # resource = packages.list 74 | # 75 | # Will look for a file name or directory matching the first occurrence: 76 | # - packages_${DIST_NAME}_{DIST_VER}_${TEMPLATE_FLAVOR}.list 77 | # - packages_${DIST_NAME}_${TEMPLATE_FLAVOR}.list 78 | # - packages_${DIST_NAME}.list 79 | # 80 | # Remark: If 'resource' is provided with full path, we use 81 | # its dirname as search directory instead of TEMPLATE_CONTENT_DIR. 82 | # ------------------------------------------------------------------------------ 83 | get_file_or_directory_for_current_flavor() { 84 | local resource="$1" 85 | local suffix="$2" 86 | local resource_dir 87 | local ext 88 | 89 | # If 'resource' is provided with full path 90 | # we use its dirname as search directory 91 | # instead of TEMPLATE_CONTENT_DIR 92 | if [ "$(dirname "${resource}")" != "." ]; then 93 | resource_dir="$(dirname "${resource}")" 94 | resource="$(basename "${resource}")" 95 | else 96 | resource_dir="${TEMPLATE_CONTENT_DIR}" 97 | fi 98 | 99 | # Determine if resource has an extension. If it has, 100 | # we save this extension and we strip it from 'resource'. 101 | if [ "${resource##*.}" != "${resource}" ]; then 102 | ext=".${resource##*.}" 103 | resource_without_ext="${resource%.*}" 104 | else 105 | ext="" 106 | resource_without_ext="${resource}" 107 | fi 108 | # shellcheck disable=SC2153 109 | if [ -n "${suffix}" ] && [ -e "${resource_dir}/${resource_without_ext}_${suffix}${ext}" ]; then 110 | file_or_directory="${resource_dir}/${resource_without_ext}_${suffix}${ext}" 111 | elif [ -e "${resource_dir}/${resource_without_ext}_${DIST_CODENAME}_${TEMPLATE_FLAVOR}${ext}" ]; then 112 | file_or_directory="${resource_dir}/${resource_without_ext}_${DIST_CODENAME}_${TEMPLATE_FLAVOR}${ext}" 113 | elif [ -e "${resource_dir}/${resource_without_ext}_${DIST_CODENAME}${ext}" ]; then 114 | file_or_directory="${resource_dir}/${resource_without_ext}_${DIST_CODENAME}${ext}" 115 | elif [ -e "${resource_dir}/${resource_without_ext}_${DIST_NAME}_${DIST_VER}_${TEMPLATE_FLAVOR}${ext}" ]; then 116 | file_or_directory="${resource_dir}/${resource_without_ext}_${DIST_NAME}_${DIST_VER}_${TEMPLATE_FLAVOR}${ext}" 117 | elif [ -e "${resource_dir}/${resource_without_ext}_${DIST_NAME}_${TEMPLATE_FLAVOR}${ext}" ]; then 118 | file_or_directory="${resource_dir}/${resource_without_ext}_${DIST_NAME}_${TEMPLATE_FLAVOR}${ext}" 119 | elif [ -e "${resource_dir}/${resource_without_ext}_${DIST_NAME}${ext}" ]; then 120 | file_or_directory="${resource_dir}/${resource_without_ext}_${DIST_NAME}${ext}" 121 | else 122 | file_or_directory="" 123 | fi 124 | echo "${file_or_directory}" 125 | } 126 | 127 | # ------------------------------------------------------------------------------ 128 | # Takes an array and exports it a global variable 129 | # 130 | # $1: Array to export 131 | # $2: Global variable name to use for export 132 | # 133 | # http://ihaveabackup.net/2012/01/29/a-workaround-for-passing-arrays-in-bash/ 134 | # 135 | # ------------------------------------------------------------------------------ 136 | setArrayAsGlobal() { 137 | local array="$1" 138 | local export_as="$2" 139 | local code 140 | local replaced 141 | code=$(declare -p "$array" 2> /dev/null || true) 142 | replaced="${code/$array/$export_as}" 143 | eval "${replaced/declare -/declare -g}" 144 | } 145 | 146 | 147 | # ------------------------------------------------------------------------------ 148 | # Checks if the passed element exists in passed array 149 | # $1: Element to check for 150 | # $2: Array to check for element in 151 | # 152 | # Returns 0 if True, or 1 if False 153 | # ------------------------------------------------------------------------------ 154 | elementIn () { 155 | local element 156 | for element in "${@:2}"; do [[ "$element" == "$1" ]] && return 0; done 157 | return 1 158 | } 159 | 160 | # ------------------------------------------------------------------------------ 161 | # Splits the path and returns an array of parts 162 | # 163 | # $1: Full path of file to split 164 | # $2: Global variable name to use for export 165 | # Returns: 166 | # ([full]='original name' [dir]='directory' [base]='filename' [ext]='extension') 167 | # 168 | # Original concept path split from: 169 | # https://stackoverflow.com/questions/965053/extract-filename-and-extension-in-bash 170 | # 171 | # ------------------------------------------------------------------------------ 172 | splitPath() { 173 | 174 | local return_global_var=$2 175 | local filename="${1##*/}" # Strip longest match of */ from start 176 | local dir="${1:0:${#1} - ${#filename}}" # Substring from 0 through pos of filename 177 | local base="${filename%.[^.]*}" # Strip shortest match of . plus at least one non-dot char from end 178 | local ext="${filename:${#base} + 1}" # Substring from len of base through end 179 | if [ "$ext" ]; then 180 | local dotext=".$ext" 181 | else 182 | local dotext="" 183 | fi 184 | if [[ -z "$base" && -n "$ext" ]]; then # If we have an extension and no base, it's really the base 185 | base=".$ext" 186 | ext="" 187 | dotext="" 188 | fi 189 | # shellcheck disable=SC2034 190 | declare -A PARTS=([full]="$1" [dir]="$dir" [base]="$base" [ext]="$ext" [dotext]="$dotext") 191 | setArrayAsGlobal PARTS "$return_global_var" 192 | } 193 | 194 | templateDirs() { 195 | local template_flavor=${1-${TEMPLATE_FLAVOR}} 196 | local template_flavor_prefix 197 | local template_flavor_dir 198 | local match=0 199 | 200 | # shellcheck disable=SC2153 201 | read -r -a template_flavor_dir <<<"${TEMPLATE_FLAVOR_DIR[@]}" 202 | 203 | for element in "${template_flavor_dir[@]}" 204 | do 205 | # (wheezy+whonix-gateway / wheezy+whonix-gateway+gnome[+++] / wheezy+gnome ) 206 | if [ "${element%:*}" == "$(templateName "${template_flavor}")" ]; then 207 | eval echo -e "${element#*:}" 208 | match=1 209 | 210 | # Very short name compare (+proxy) 211 | elif [ "${element:0:1}" == "+" ] && [ "${element%:*}" == "+${template_flavor}" ]; then 212 | eval echo -e "${element#*:}" 213 | match=1 214 | 215 | # Generic template directory that matches all flavors, or even no flavors 216 | elif [ "${element:0:1}" == "*" ]; then 217 | eval echo -e "${element#*:}" 218 | match=1 219 | fi 220 | done 221 | 222 | if [ "${match}" -eq 1 ]; then 223 | return 224 | fi 225 | 226 | template_flavor_prefix="$(templateFlavorPrefix "${template_flavor}")" 227 | if [ -n "${template_flavor}" ] && [ "${template_flavor}" == "+" ]; then 228 | local dir="${TEMPLATE_CONTENT_DIR}/${template_flavor_prefix}" 229 | elif [ -n "${template_flavor}" ]; then 230 | local dir="${TEMPLATE_CONTENT_DIR}/${template_flavor_prefix}${template_flavor}" 231 | else 232 | local dir="${TEMPLATE_CONTENT_DIR}" 233 | fi 234 | 235 | echo "${dir}" 236 | } 237 | 238 | exists() { 239 | filename="${1}" 240 | 241 | if [ -e "${filename}" ] && ! elementIn "${filename}" "${GLOBAL_CACHE[@]}"; then 242 | # Cache $script 243 | # 244 | # GLOBAL_CACHE is declared in the `getFileLocations` function and is later 245 | # renamed to a name passed into the function as $1 to allow scripts using 246 | # the function to have access to the array 247 | GLOBAL_CACHE["${#GLOBAL_CACHE[@]}"]="${filename}" 248 | return 0 249 | fi 250 | return 1 251 | } 252 | 253 | templateFile() { 254 | local file="$1" 255 | local suffix="$2" 256 | local template_flavor="$3" 257 | local template_dirs 258 | 259 | template_dirs="$(templateDirs "${template_flavor}")" 260 | 261 | splitPath "${file}" path_parts 262 | 263 | for template_dir in "${template_dirs[@]}"; do 264 | # No template flavor 265 | if [ -z "${template_flavor}" ] || [ "${template_flavor}" = "+" ]; then 266 | if [ -n "${suffix}" ]; then 267 | # shellcheck disable=SC2154 268 | exists "${TEMPLATE_CONTENT_DIR}/${path_parts[base]}_${suffix}${path_parts[dotext]}" || true 269 | else 270 | exists "${TEMPLATE_CONTENT_DIR}/${path_parts[base]}${path_parts[dotext]}" || true 271 | fi 272 | return 273 | fi 274 | 275 | # Locate file in directory named after flavor 276 | if [ -n "${suffix}" ]; then 277 | # Append suffix to filename (before extension) 278 | # `minimal` is the template_flavor being used in comment example 279 | 280 | # (TEMPLATE_FLAVOR_DIR/minimal/packages_qubes_suffix.list) 281 | exists "${template_dir}/${template_flavor}/${path_parts[base]}_${suffix}${path_parts[dotext]}" || true 282 | 283 | # (TEMPLATE_FLAVOR_DIR/minimal/packages_qubes_suffix.list) 284 | exists "${template_dir}/${template_flavor}/${path_parts[base]}_${suffix}${path_parts[dotext]}" || true 285 | 286 | # (TEMPLATE_FLAVOR_DIR/packages_qubes_suffix.list) 287 | exists "${template_dir}/${path_parts[base]}_${suffix}${path_parts[dotext]}" || true 288 | 289 | # (TEMPLATE_FLAVOR_DIR/packages_qubes_minimal_suffix.list) 290 | exists "${template_dir}/${path_parts[base]}_${suffix}_${template_flavor}${path_parts[dotext]}" || true 291 | 292 | # (TEMPLATE_CONTENT_DIR/packages_qubes_minimal_suffix.list) 293 | exists "${TEMPLATE_CONTENT_DIR}/${path_parts[base]}_${suffix}_${template_flavor}${path_parts[dotext]}" || true 294 | else 295 | # (TEMPLATE_FLAVOR_DIR/minimal/packages_qubes.list) 296 | exists "${template_dir}/${template_flavor}/${path_parts[base]}${path_parts[dotext]}" || true 297 | 298 | # (TEMPLATE_FLAVOR_DIR/minimal/packages_qubes_minimal.list) 299 | exists "${template_dir}/${template_flavor}/${path_parts[base]}_${template_flavor}${path_parts[dotext]}" || true 300 | 301 | # (TEMPLATE_FLAVOR_DIR/packages_qubes.list) 302 | exists "${template_dir}/${path_parts[base]}${path_parts[dotext]}" || true 303 | 304 | # (TEMPLATE_FLAVOR_DIR/packages_qubes_minimal.list) 305 | exists "${template_dir}/${path_parts[base]}_${template_flavor}${path_parts[dotext]}" || true 306 | 307 | # (TEMPLATE_CONTENT_DIR/packages_qubes_minimal.list) 308 | exists "${TEMPLATE_CONTENT_DIR}/${path_parts[base]}_${template_flavor}${path_parts[dotext]}" || true 309 | fi 310 | done 311 | } 312 | 313 | copyTreeExec() { 314 | local source_dir="$1" 315 | local dir="$2" 316 | local template_flavor="$3" 317 | local target_dir="$4" 318 | local template_dirs 319 | 320 | template_dirs="$(templateDirs "${template_flavor}")" 321 | 322 | for template_dir in "${template_dirs[@]}"; do 323 | local source_dir 324 | local target_dir 325 | 326 | source_dir="$(readlink -m "${source_dir:-${template_dir}}/${dir}")" 327 | target_dir="$(readlink -m "${target_dir:-${INSTALL_DIR}}")" 328 | 329 | if ! [ -d "${source_dir}" ]; then 330 | debug "No extra files to copy for ${dir}" 331 | return 0 332 | fi 333 | 334 | debug "Copying ${source_dir}/* ${target_dir}" 335 | cp -rp "${source_dir}/." "${target_dir}" 336 | 337 | if [ -f "${source_dir}/.facl" ]; then 338 | debug "Restoring file permissions..." 339 | pushd "${target_dir}" 340 | { 341 | setfacl --restore="${source_dir}/.facl" 2>/dev/null ||: 342 | rm -f .facl 343 | } 344 | popd 345 | fi 346 | done 347 | } 348 | 349 | callTemplateFunction() { 350 | local calling_script="$1" 351 | local calling_arg="$2" 352 | local functionExec="$3" 353 | local template_flavor="${TEMPLATE_FLAVOR}" 354 | local template_options 355 | 356 | ${functionExec} "${calling_script}" \ 357 | "${calling_arg}" \ 358 | "${template_flavor}" 359 | 360 | # Find a $DIST sub-directory 361 | ${functionExec} "${calling_script}" \ 362 | "${calling_arg}" \ 363 | "+" 364 | 365 | read -r -a template_options <<<"${TEMPLATE_OPTIONS[@]}" 366 | 367 | for option in "${template_options[@]}" 368 | do 369 | # Long name (wheezy+whonix-gateway+proxy) 370 | ${functionExec} "${calling_script}" \ 371 | "${calling_arg}" \ 372 | "${TEMPLATE_FLAVOR}+${option}" 373 | 374 | # Short name (wheezy+proxy) 375 | ${functionExec} "${calling_script}" \ 376 | "${calling_arg}" \ 377 | "${option}" 378 | done 379 | 380 | # If template_flavor exists, also check on base distro 381 | if [ -n "${template_flavor}" ]; then 382 | ${functionExec} "${calling_script}" \ 383 | "${calling_arg}" 384 | fi 385 | } 386 | 387 | # ------------------------------------------------------------------------------ 388 | # Will return all files that match pattern of suffix 389 | # Example: 390 | # filename = packages.list 391 | # suffix = ${DIST} (wheezy) 392 | # 393 | # Will look for a file name packages_wheezy.list in: 394 | # the $TEMPLATE_CONTENT_DIR; beside original 395 | # the $TEMPLATE_CONTENT_DIR/$DIST (wheezy) directory 396 | # any included template module directories ($TEMPLATE_CONTENT_DIR/gnome) 397 | # 398 | # All matches are returned and each will be able to be used 399 | # ------------------------------------------------------------------------------ 400 | getFileLocations() { 401 | local return_global_var=$1 402 | local filename="$2" 403 | local suffix="$3" 404 | local function="templateFile" 405 | 406 | unset GLOBAL_CACHE 407 | declare -gA GLOBAL_CACHE 408 | 409 | callTemplateFunction "${filename}" "${suffix}" "${function}" 410 | setArrayAsGlobal GLOBAL_CACHE "$return_global_var" 411 | 412 | if [ ! ${#GLOBAL_CACHE[@]} -eq 0 ]; then 413 | debug "Smart files located for: '${filename##*/}' (suffix: ${suffix}):" 414 | for filename in "${GLOBAL_CACHE[@]}"; do 415 | debug "${filename}" 416 | done 417 | fi 418 | } 419 | 420 | # ------------------------------------------------------------------------------ 421 | # Executes any additional optional configuration steps if the configuration 422 | # scripts exist 423 | # 424 | # Will find all scripts with 425 | # Example: 426 | # filename = 04_install_qubes.sh 427 | # suffix = post 428 | # 429 | # Will look for a file name 04_install_qubes_post in: 430 | # the $TEMPLATE_CONTENT_DIR; beside original 431 | # the $TEMPLATE_CONTENT_DIR/$DIST (wheezy) directory 432 | # any included template module directories ($TEMPLATE_CONTENT_DIR/gnome) 433 | # 434 | # All matches are executed 435 | # ------------------------------------------------------------------------------ 436 | buildStep() { 437 | local filename="$1" 438 | local suffix="$2" 439 | unset build_step_files 440 | 441 | info "Locating buildStep files: ${filename##*/} suffix: ${suffix}" 442 | getFileLocations "build_step_files" "${filename}" "${suffix}" 443 | 444 | # shellcheck disable=SC2154 445 | for script in "${build_step_files[@]}"; do 446 | if [ "${script}" == "${filename}" ]; then 447 | error "Recursion detected!" 448 | exit 1 449 | fi 450 | if [ -e "${script}" ]; then 451 | # Test module expects raw output back only used to asser test results 452 | if [[ -n ${TEST} ]]; then 453 | echo "${script}" 454 | else 455 | info "Currently running script: ${script}" 456 | fi 457 | # Execute $script 458 | "${script}" 459 | fi 460 | done 461 | } 462 | 463 | # ------------------------------------------------------------------------------ 464 | # Copy extra file tree to ${INSTALL_DIR} 465 | # TODO: Allow copy per step (04_install_qubes.sh-files) 466 | # 467 | # To set file permissions is a PITA since git won't save them and will 468 | # complain heavily if they are set to root only read, so this is the procdure: 469 | # 470 | # 1. Change to the directory that you want to have file permissions retained 471 | # 2. Change all the file permissions / ownership as you want 472 | # 3. Change back to the root of the exta directory (IE: extra-qubes-files) 473 | # 4. Manually restore facl's: setfacl --restore=.facl 474 | # 5. Manually create facl backup used after copying: getfacl -R . > .facl 475 | # 6. If git complains; reset file ownership back to user. The .facl file stored 476 | # the file permissions and will be used to reset the file permissions after 477 | # they get copied over to ${INSTALL_DIR} 478 | # NOTE: Don't forget to redo this process if you add -OR- remove files 479 | # ------------------------------------------------------------------------------ 480 | copyTree() { 481 | local dir="$1" 482 | local source_dir="$2" 483 | local target_dir="$3" 484 | local function="copyTreeExec" 485 | 486 | if [ -z "${source_dir}" ]; then 487 | splitPath "${0}" path_parts 488 | if [ -d "${path_parts[dir]}/${dir}" ]; then 489 | copyTreeExec "${path_parts[dir]}" "${dir}" "" "" 490 | else 491 | callTemplateFunction "" "${dir}" "${function}" 492 | fi 493 | else 494 | copyTreeExec "${source_dir}" "${dir}" "" "${target_dir}" 495 | fi 496 | } 497 | 498 | # $0 is module that sourced vars.sh 499 | info "Currently running script: ${0}" 500 | -------------------------------------------------------------------------------- /mount_root.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ROOTIMG=$1 3 | 4 | if [ x$ROOTIMG = x ] ; then 5 | echo "usage: $0 " 6 | exit 0 7 | fi 8 | 9 | # We assume that the input root.img has the following structure: 10 | 11 | # /dev/sda1 <--- root fs 12 | # /dev/sda2 <--- swap 13 | 14 | # and that the first partition starts at offset 63*512 from the begging of the image file 15 | OFFSET=$((63*512)) 16 | 17 | mkdir -p mnt 18 | 19 | MNTDIR=$(pwd)/mnt 20 | 21 | LOOP=$(/sbin/losetup -f --show -o $OFFSET $ROOTIMG) 22 | 23 | if [ x$LOOP = x ] ; then 24 | echo "Cannot setup loopback device for the $ROOTIMG file -- perhaps a permissions problem?" 25 | exit 1 26 | fi 27 | 28 | mount $LOOP $MNTDIR || { 29 | echo "Cannot mount $LOOP to $MNTDIR" 30 | /sbin/losetup -d $LOOP 31 | exit 2 32 | } 33 | 34 | # generate unmount script 35 | BASENAE=$(basename $ROOTIMG) 36 | UNMOUNT_SCRIPT=$(echo unmount_root-$BASENAE.sh) 37 | echo "#!/bin/sh" > $UNMOUNT_SCRIPT 38 | echo "umount $MNTDIR || { echo \"Cannot unmount!\"; exit 1; }" >> $UNMOUNT_SCRIPT 39 | echo "/sbin/losetup -d $LOOP || { echo \"Cannot delete the loop device\"; exit 1; }" >> $UNMOUNT_SCRIPT 40 | echo "rm -f $UNMOUNT_SCRIPT" >> $UNMOUNT_SCRIPT 41 | chmod +x $UNMOUNT_SCRIPT 42 | 43 | -------------------------------------------------------------------------------- /prepare_image: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "${VERBOSE}" -ge 2 -o "${DEBUG}" == "1" ]; then 4 | set -x 5 | fi 6 | 7 | set -e 8 | 9 | # ------------------------------------------------------------------------------ 10 | # Configurations 11 | # ------------------------------------------------------------------------------ 12 | export IMG="${1}" 13 | export LC_ALL=POSIX 14 | 15 | RETCODE=0 16 | 17 | : ${DIST=fc14} 18 | 19 | . ./builder_setup >/dev/null 20 | . ./umount_kill.sh >/dev/null 21 | 22 | if ! [ $# -eq 1 ]; then 23 | echo "usage ${0} " 24 | exit 25 | fi 26 | 27 | if [ "${VERBOSE}" == "1" ]; then 28 | export YUM_OPTS="${YUM_OPTS} -q" 29 | fi 30 | 31 | if [ -z "$TEMPLATE_ROOT_SIZE" ]; then 32 | TEMPLATE_ROOT_SIZE=10G 33 | fi 34 | 35 | # ------------------------------------------------------------------------------ 36 | # Make sure INSTALLDIR exists 37 | # ------------------------------------------------------------------------------ 38 | export INSTALLDIR="$(readlink -m mnt)" 39 | mkdir -p "${INSTALLDIR}" 40 | 41 | # Support for builderv2 42 | export INSTALL_DIR="${INSTALLDIR}" 43 | # shellcheck disable=SC2153 44 | export CACHE_DIR="${CACHEDIR}" 45 | 46 | # ------------------------------------------------------------------------------ 47 | # Prepare for mount 48 | # ------------------------------------------------------------------------------ 49 | echo "-> Preparing instalation of ${DIST} template..." 50 | "${SCRIPTSDIR}/00_prepare.sh" 51 | 52 | # ------------------------------------------------------------------------------ 53 | # Mount image and install core OS 54 | # ------------------------------------------------------------------------------ 55 | if [ -f "${IMG}" ]; then 56 | echo "-> Image file already exists, assuming *update*..." 57 | if [ "0$TEMPLATE_ROOT_WITH_PARTITIONS" -eq 1 ]; then 58 | IMG_LOOP=$(/sbin/losetup -P -f --show "$IMG") 59 | IMG_DEV=${IMG_LOOP}p3 60 | else 61 | IMG_LOOP=$(/sbin/losetup -f --show "$IMG") 62 | IMG_DEV=${IMG_LOOP} 63 | fi 64 | udevadm settle --exit-if-exists="$IMG_DEV" 65 | else 66 | echo "-> Initializing empty image..." 67 | truncate -s "$TEMPLATE_ROOT_SIZE" "${IMG}" || exit 1 68 | 69 | if [ "0$TEMPLATE_ROOT_WITH_PARTITIONS" -eq 1 ]; then 70 | echo "-> Creating partition table" 71 | # have static UUIDs to make partition table reproducible 72 | /usr/sbin/sfdisk "$IMG" < Creating filesystem..." 90 | /sbin/mkfs.ext4 -q -F "${IMG_DEV}" || exit 1 91 | fi 92 | 93 | mount "${IMG_DEV}" "${INSTALLDIR}" || exit 1 94 | trap "umount_kill $(readlink -m ${INSTALLDIR})" EXIT 95 | "${SCRIPTSDIR}/01_install_core.sh" 96 | 97 | # ------------------------------------------------------------------------------ 98 | # Install package groups 99 | # ------------------------------------------------------------------------------ 100 | echo "-> Installing package groups..." 101 | "${SCRIPTSDIR}/02_install_groups.sh" 102 | 103 | # ------------------------------------------------------------------------------ 104 | # Cleanup 105 | # ------------------------------------------------------------------------------ 106 | trap - EXIT 107 | 108 | echo "-> Unmounting prepared_image..." 109 | umount_kill "$(readlink -m ${INSTALLDIR})" || true 110 | /sbin/losetup -d ${IMG_LOOP} 111 | 112 | exit ${RETCODE} 113 | -------------------------------------------------------------------------------- /prepared_images/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /qubeize_image: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$VERBOSE" -ge 2 -o "$DEBUG" == "1" ]; then 4 | set -x 5 | fi 6 | 7 | set -e 8 | 9 | # ------------------------------------------------------------------------------ 10 | # Configurations and Conditionals 11 | # ------------------------------------------------------------------------------ 12 | export CLEANIMG="$1" 13 | export NAME="$2" 14 | export LC_ALL=POSIX 15 | 16 | . ./builder_setup >/dev/null 17 | . ./umount_kill.sh >/dev/null 18 | 19 | if [ $# -eq 0 ]; then 20 | echo "usage $0 " 21 | exit 22 | fi 23 | 24 | if [ "x$CLEANIMG" = x ]; then 25 | echo "Image file not specified!" 26 | exit 1 27 | fi 28 | 29 | if [ "x$NAME" = x ]; then 30 | echo "Name not given!" 31 | exit 1 32 | fi 33 | 34 | ID=$(id -ur) 35 | 36 | if [ $ID != 0 ] ; then 37 | echo "This script should be run as root user." 38 | exit 1 39 | fi 40 | 41 | if [ "$VERBOSE" == "1" ]; then 42 | export YUM_OPTS="$YUM_OPTS -q" 43 | fi 44 | 45 | # ------------------------------------------------------------------------------ 46 | # Cleanup function 47 | # ------------------------------------------------------------------------------ 48 | function cleanup() { 49 | errval=$? 50 | trap - ERR 51 | trap 52 | umount_kill "$PWD/mnt" || true 53 | /sbin/losetup -d ${IMG_LOOP} 54 | exit $errval 55 | } 56 | trap cleanup ERR 57 | 58 | # ------------------------------------------------------------------------------ 59 | # Mount qubeized_image 60 | # ------------------------------------------------------------------------------ 61 | export IMG="qubeized_images/$NAME/root.img" 62 | mkdir -p "qubeized_images/$NAME" 63 | 64 | if [ "0$DISCARD_PREPARED_IMAGE" -eq "1" ]; then 65 | echo "--> Moving $CLEANIMG to $IMG..." 66 | mv "$CLEANIMG" "$IMG" || exit 1 67 | else 68 | echo "--> Copying $CLEANIMG to $IMG..." 69 | cp "$CLEANIMG" "$IMG" || exit 1 70 | fi 71 | 72 | echo "--> Mounting $IMG" 73 | mkdir -p mnt 74 | if [ "0$TEMPLATE_ROOT_WITH_PARTITIONS" -eq 1 ]; then 75 | IMG_LOOP=$(/sbin/losetup -P -f --show "$IMG") 76 | IMG_DEV=${IMG_LOOP}p3 77 | else 78 | IMG_LOOP=$(/sbin/losetup -f --show "$IMG") 79 | IMG_DEV=${IMG_LOOP} 80 | fi 81 | udevadm settle --exit-if-exists="$IMG_DEV" 82 | mount "$IMG_DEV" mnt || exit 1 83 | export INSTALLDIR=mnt 84 | 85 | # prepare for template.conf, so the qubeize script may generate it dynamically 86 | export TEMPLATE_CONF="${PWD}/template.conf" 87 | rm -f "$TEMPLATE_CONF" 88 | 89 | # Support for builderv2 90 | export INSTALL_DIR="${INSTALLDIR}" 91 | # shellcheck disable=SC2153 92 | export CACHE_DIR="${CACHEDIR}" 93 | 94 | # ------------------------------------------------------------------------------ 95 | # Run qubeize script 96 | # ------------------------------------------------------------------------------ 97 | "$SCRIPTSDIR/04_install_qubes.sh" 98 | 99 | # ------------------------------------------------------------------------------ 100 | # Create App Menus 101 | # ------------------------------------------------------------------------------ 102 | echo "--> Choosing appmenus whitelists..." 103 | _appmenus_dir="${APPMENUS_DIR:-${SCRIPTSDIR}}" 104 | rm -f appmenus 105 | if [ -d "${_appmenus_dir}/appmenus_${DIST}_${TEMPLATE_FLAVOR}" ]; then 106 | ln -s "${_appmenus_dir}/appmenus_${DIST}_${TEMPLATE_FLAVOR}" appmenus 107 | elif [ -d "${_appmenus_dir}/appmenus_${DIST//[0-9]*}_${TEMPLATE_FLAVOR}" ]; then 108 | ln -s "${_appmenus_dir}/appmenus_${DIST//[0-9]*}_${TEMPLATE_FLAVOR}" appmenus 109 | elif [ -d "${_appmenus_dir}/appmenus_$DIST" ]; then 110 | ln -s "${_appmenus_dir}/appmenus_$DIST" appmenus 111 | elif [ -d "${_appmenus_dir}/appmenus_${DIST//[0-9]*}" ]; then 112 | ln -s "${_appmenus_dir}/appmenus_${DIST//[0-9]*}" appmenus 113 | elif [ -d "${_appmenus_dir}/appmenus" ]; then 114 | ln -s "${_appmenus_dir}/appmenus" appmenus 115 | else 116 | ln -s "appmenus_generic" appmenus 117 | fi 118 | 119 | # ------------------------------------------------------------------------------ 120 | # Create Template Config File 121 | # ------------------------------------------------------------------------------ 122 | 123 | echo "--> Creating template config file..." 124 | if ! [ -e "$TEMPLATE_CONF" ]; then 125 | _conf_dir="${CONFIG_DIR:-${SCRIPTSDIR}}" 126 | if [ -f "${_conf_dir}/template_${DIST}_${TEMPLATE_FLAVOR}.conf" ]; then 127 | cp "${_conf_dir}/template_${DIST}_${TEMPLATE_FLAVOR}.conf" "$TEMPLATE_CONF" 128 | elif [ -f "${_conf_dir}/template_${DIST//[0-9]*}_${TEMPLATE_FLAVOR}.conf" ]; then 129 | cp "${_conf_dir}/template_${DIST//[0-9]*}_${TEMPLATE_FLAVOR}.conf" "$TEMPLATE_CONF" 130 | elif [ -f "${_conf_dir}/template_$DIST.conf" ]; then 131 | cp "${_conf_dir}/template_$DIST.conf" "$TEMPLATE_CONF" 132 | elif [ -f "${_conf_dir}/template_${DIST//[0-9]*}.conf" ]; then 133 | cp "${_conf_dir}/template_${DIST//[0-9]*}.conf" "$TEMPLATE_CONF" 134 | elif [ -f "${_conf_dir}/template.conf" ]; then 135 | cp "${_conf_dir}/template.conf" "$TEMPLATE_CONF" 136 | else 137 | cp template_generic.conf "$TEMPLATE_CONF" 138 | fi 139 | fi 140 | 141 | # ------------------------------------------------------------------------------ 142 | # Link directories so they can be mounted 143 | # ------------------------------------------------------------------------------ 144 | echo "--> Linking /home to /rw/home..." 145 | mv mnt/home mnt/home.orig 146 | mkdir mnt/home 147 | 148 | echo "--> Linking /usr/local to /rw/usrlocal..." 149 | mv mnt/usr/local mnt/usr/local.orig 150 | mkdir mnt/usr/local 151 | 152 | echo "Reducing image size (calling cleanup_image)..." 153 | ls -als $IMG 154 | ./cleanup_image "$INSTALLDIR" 155 | ls -als $IMG 156 | 157 | # ------------------------------------------------------------------------------ 158 | # Finsh - unmount image 159 | # ------------------------------------------------------------------------------ 160 | echo "--> Unmounting $IMG" 161 | umount_kill "$PWD/mnt" || true 162 | /sbin/losetup -d ${IMG_LOOP} 163 | 164 | echo "Qubeized image stored at: $IMG" 165 | 166 | chown -R --reference=. qubeized_images/$NAME 167 | -------------------------------------------------------------------------------- /qubeized_images/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /rpm/.gitignore: -------------------------------------------------------------------------------- 1 | noarch/ 2 | -------------------------------------------------------------------------------- /rpmbuild/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /template_generic.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QubesOS/qubes-linux-template-builder/44e9d8f979ecb965d8ab32d53fd49e0ea31a1175/template_generic.conf -------------------------------------------------------------------------------- /templates.spec: -------------------------------------------------------------------------------- 1 | # 2 | # This SPEC is for bulding RPM packages that contain complete Qubes Template files 3 | # This includes the VM's root image, patched with all qubes rpms, etc 4 | # 5 | 6 | %{!?template_name: %global template_name %{getenv:TEMPLATE_NAME}} 7 | %{!?version: %global version %(cat version)} 8 | %{!?rel: %global rel %(cat build_timestamp_%{template_name} || echo unavailable)} 9 | 10 | Name: qubes-template-%{template_name} 11 | Version: %{version} 12 | Release: %{rel} 13 | Summary: Qubes template for %{template_name} 14 | 15 | License: GPL 16 | URL: http://www.qubes-os.org 17 | Source: . 18 | 19 | Requires: xdg-utils 20 | Requires(post): tar 21 | BuildArch: noarch 22 | Provides: qubes-template 23 | Obsoletes: %{name} > %{version}-%{release} 24 | 25 | %define _builddir %(pwd) 26 | %define _rpmdir %(pwd)/rpm 27 | %define dest_dir /var/lib/qubes/vm-templates/%{template_name} 28 | 29 | %define _binaries_in_noarch_packages_terminate_build 0 30 | %description 31 | Qubes template for %{template_name} 32 | 33 | %build 34 | cd qubeized_images/%{template_name} 35 | rm -f root.img.part.* 36 | tar --sparse --dereference -cf - root.img | split -d -b 1G - root.img.part. 37 | 38 | if [ "0$DISCARD_PREPARED_IMAGE" -eq 1 ]; then 39 | rm -f root.img 40 | fi 41 | 42 | %install 43 | rm -rf $RPM_BUILD_ROOT 44 | mkdir -p $RPM_BUILD_ROOT/%{dest_dir} 45 | for i in qubeized_images/%{template_name}/root.img.part.* ; do mv $i $RPM_BUILD_ROOT/%{dest_dir}/ ; done 46 | touch $RPM_BUILD_ROOT/%{dest_dir}/root.img # we will create the real file in %post 47 | touch $RPM_BUILD_ROOT/%{dest_dir}/private.img # we will create the real file in %post 48 | touch $RPM_BUILD_ROOT/%{dest_dir}/volatile.img # we will create the real file in %post 49 | touch $RPM_BUILD_ROOT/%{dest_dir}/clean-volatile.img.tar # we will create the real file in %post 50 | 51 | mkdir -p $RPM_BUILD_ROOT/%{dest_dir}/apps.templates 52 | mkdir -p $RPM_BUILD_ROOT/%{dest_dir}/apps.tempicons 53 | mkdir -p $RPM_BUILD_ROOT/%{dest_dir}/apps 54 | cp appmenus/whitelisted-appmenus.list appmenus/vm-whitelisted-appmenus.list $RPM_BUILD_ROOT/%{dest_dir}/ 55 | cp appmenus/netvm-whitelisted-appmenus.list $RPM_BUILD_ROOT/%{dest_dir}/ 56 | cp template.conf $RPM_BUILD_ROOT/%{dest_dir}/ 57 | 58 | %pre 59 | 60 | export XDG_DATA_DIRS=/usr/share/ 61 | if [ "$1" -gt 1 ] ; then 62 | # upgrading already installed template... 63 | # avoid removing innocent files if *.desktop doesn't mach anything 64 | # https://bugs.freedesktop.org/105635 65 | if ls %{dest_dir}/apps/*.directory %{dest_dir}/apps/*.desktop >/dev/null 2>&1; then 66 | echo "--> Removing previous menu shortcuts..." 67 | xdg-desktop-menu uninstall --mode system \ 68 | %{dest_dir}/apps/*.directory %{dest_dir}/apps/*.desktop 69 | fi 70 | fi 71 | 72 | 73 | %post 74 | 75 | if command -v qvm-template-postprocess >/dev/null 2>&1; then 76 | qvm-template-postprocess --really post-install %{template_name} %{dest_dir} 77 | exit $? 78 | fi 79 | 80 | echo "--> Processing the root.img... (this might take a while)" 81 | rm -f %{dest_dir}/root.img 82 | cat %{dest_dir}/root.img.part.* | tar --sparse -xf - -C %{dest_dir} 83 | rm -f %{dest_dir}/root.img.part.* 84 | chown root.qubes %{dest_dir}/root.img 85 | chmod 0660 %{dest_dir}/root.img 86 | 87 | echo "--> Processing the volatile.img..." 88 | /usr/lib/qubes/prepare-volatile-img.sh %{dest_dir}/volatile.img $[ `stat -c '%s' %{dest_dir}/root.img` / 1024 / 1024 ] || exit 1 89 | chown root.qubes %{dest_dir}/volatile.img 90 | chmod 0660 %{dest_dir}/volatile.img 91 | tar --sparse -cf %{dest_dir}/clean-volatile.img.tar -C %{dest_dir} volatile.img 92 | chown root.qubes %{dest_dir}/clean-volatile.img.tar 93 | chmod 0660 %{dest_dir}/clean-volatile.img.tar 94 | 95 | if [ "$1" = 1 ] ; then 96 | # installing for the first time 97 | echo "--> Creating private.img..." 98 | truncate -s 2G %{dest_dir}/private.img 99 | mkfs.ext4 -m 0 -q -F %{dest_dir}/private.img 100 | chown root.qubes %{dest_dir}/private.img 101 | chmod 0660 %{dest_dir}/private.img 102 | fi 103 | 104 | 105 | export XDG_DATA_DIRS=/usr/share/ 106 | 107 | echo "--> Instaling menu shortcuts..." 108 | 109 | local_user=`getent group qubes | cut -d : -f 4 | cut -d , -f 1` 110 | if [ -n "$local_user" ]; then 111 | call_as_user() { 112 | su -c "$*" - $local_user 113 | } 114 | else 115 | # This will be the case during installation - user will be created in 116 | # firstboot. There is also a code to fix file permissions, so not a big problem 117 | call_as_user() { 118 | $* 119 | } 120 | fi 121 | 122 | if [ "$1" = 1 ] ; then 123 | # installing for the first time 124 | call_as_user qvm-add-template --rpm %{template_name} 125 | fi 126 | 127 | # If running inside of chroot (means - from anaconda), force offline mode 128 | if [ "`stat -c %d:%i /`" != "`stat -c %d:%i /proc/1/root/.`" ]; then 129 | qvm-template-commit --offline-mode %{template_name} 130 | call_as_user /usr/libexec/qubes-appmenus/create-apps-for-appvm.sh \ 131 | %{dest_dir}/apps.templates %{template_name} vm-templates appvm-black 132 | else 133 | qvm-template-commit %{template_name} 134 | qvm-prefs --force-root -s %{template_name} netvm none 135 | qvm-start --no-guid %{template_name} 136 | call_as_user qvm-sync-appmenus --force-root %{template_name} 137 | qvm-shutdown --wait %{template_name} 138 | qvm-prefs --force-root -s %{template_name} netvm default 139 | # restore default firewall settings, which was reset by setting netvm=none 140 | rm -f %{dest_dir}/firewall.xml 141 | chgrp -R qubes %{dest_dir} 142 | chmod g+rwX -R %{dest_dir} 143 | fi 144 | exit 0 145 | 146 | %preun 147 | if [ "$1" = 0 ] ; then 148 | # no more packages left 149 | 150 | if command -v qvm-template-postprocess >/dev/null 2>&1; then 151 | qvm-template-postprocess --really pre-remove %{template_name} %{dest_dir} 152 | exit $? 153 | fi 154 | 155 | # First remove DispVM template (even if not exists...) 156 | qvm-remove --force-root -q %{template_name}-dvm 157 | 158 | if ! qvm-remove --force-root -q --just-db %{template_name}; then 159 | exit 1 160 | fi 161 | 162 | rm -f %{dest_dir}/root-cow.img 163 | rm -f %{dest_dir}/root-cow.img.old 164 | rm -f %{dest_dir}/firewall.xml 165 | rm -f %{dest_dir}/%{template_name}.conf 166 | rm -f %{dest_dir}/updates.stat 167 | 168 | # we need to have it here, because rpm -U