From cd96dcb95a95bb590b4b1d2f8e564b273e05e0dd Mon Sep 17 00:00:00 2001 From: build Date: Fri, 4 Mar 2022 14:33:13 -0500 Subject: [PATCH] Initial commit --- Makefile.alpinelinux | 196 ++++++++++++++++++++++++++++++++++ Makefile.builder | 8 ++ prepare-chroot-base | 36 +++++++ prepare-chroot-builder | 81 ++++++++++++++ scripts/00_prepare.sh | 34 ++++++ scripts/01_install_core.sh | 17 +++ scripts/04_install_qubes.sh | 112 +++++++++++++++++++ scripts/09_cleanup.sh | 40 +++++++ scripts/alpine-chroot | 144 +++++++++++++++++++++++++ scripts/network | 1 + scripts/packages.list | 46 ++++++++ scripts/packages_minimal.list | 10 ++ scripts/resolv.conf | 1 + 13 files changed, 726 insertions(+) create mode 100644 Makefile.alpinelinux create mode 100644 Makefile.builder create mode 100755 prepare-chroot-base create mode 100755 prepare-chroot-builder create mode 100755 scripts/00_prepare.sh create mode 100755 scripts/01_install_core.sh create mode 100755 scripts/04_install_qubes.sh create mode 100755 scripts/09_cleanup.sh create mode 100755 scripts/alpine-chroot create mode 100644 scripts/network create mode 100644 scripts/packages.list create mode 100644 scripts/packages_minimal.list create mode 100644 scripts/resolv.conf diff --git a/Makefile.alpinelinux b/Makefile.alpinelinux new file mode 100644 index 0000000..363f075 --- /dev/null +++ b/Makefile.alpinelinux @@ -0,0 +1,196 @@ +# Makefile for Archlinux packages build +# +# For "API" documentation check Makefile.generic +# +# Variables supposed to be in component's Makefile.builder: +# ALPINE_BUILD_DIRS - list of alpinelinux directories containing build sripts (PKGFILES...) + +### Variables required as per Makefile.generic: +# +# PACKAGE_LIST - list of packages to build. Targets 'build-dep', 'package' and 'copy-out' +# will be run for each word on the list, with PACKAGE set to current word +# DIST_BUILD_DIR - basedir for sources inside of chroot - relative to +# CHROOT_DIR (qubes-src will be created in this directory) +# +PACKAGE_LIST = $(ALPINE_BUILD_DIRS) +DIST_BUILD_DIR = /home/user + +### Local variables +RUN_AS_USER = user + +ALPINELINUX_MIRROR ?= https://dl-cdn.alpinelinux.org/alpine + +DEBUG ?= 0 +ifneq ($(DEBUG),0) + $(info ╔══ DEBUG ══════════════════════════════════════════════════════════════════════) + $(info ║ Repo Variables) + $(info ╠───────────────────────────────────────────────────────────────────────────────) + $(info ║ SRC_DIR: $(SRC_DIR)) # qubes-src + $(info ║ CHROOT_DIR: $(CHROOT_DIR)) # /home/user/qubes-builder/chroot-alpinelinux + $(info ║ BUILDER_REPO_DIR: $(BUILDER_REPO_DIR)) # /home/user/qubes-builder/qubes-packages-mirror-repo/alpinelinux + $(info ╠───────────────────────────────────────────────────────────────────────────────) + $(info ║ Chroot Variables) + $(info ╠───────────────────────────────────────────────────────────────────────────────) + $(info ║ CHROOT_DIR: $(CHROOT_DIR)) # + $(info ║ DIST_BUILD_DIR: $(DIST_BUILD_DIR)) # /home/user + $(info ║ DIST_SRC: $(DIST_SRC)) # /home/user/qubes-src/repo + $(info ╠───────────────────────────────────────────────────────────────────────────────) + $(info ║ Build Variables) + $(info ╠───────────────────────────────────────────────────────────────────────────────) + $(info ║ ALPINELINUX_PLUGIN_DIR: $(ALPINELINUX_PLUGIN_DIR)) # /home/user/qubes-builder/qubes-src/builder-alpinelinux + $(info ║ CACHEDIR: $(CACHEDIR)) # cache/alpinelinux + $(info ║ PACKAGE_LIST: $(PACKAGE_LIST)) # alpinelinux + $(info ║ DISTRIBUTION: $(DISTRIBUTION)) # alpinelinux + $(info ║ DIST: $(DIST)) # + $(info ║ COMPONENT: $(COMPONENT)) # + $(info ║ PACKAGE_SET: $(PACKAGE_SET)) # vm + $(info ║ CHROOT_ENV: $(CHROOT_ENV)) # BACKEND_VMM=xen + $(info ╠───────────────────────────────────────────────────────────────────────────────) + $(info ║ Repository Variables) + $(info ╠───────────────────────────────────────────────────────────────────────────────) + $(info ║ UPDATE_REPO: $(UPDATE_REPO)) # + $(info ║ TARGET_REPO: $(TARGET_REPO)) # + $(info ║ SNAPSHOT_REPO: $(SNAPSHOT_REPO)) # + $(info ║ SNAPSHOT_FILE: $(SNAPSHOT_FILE)) # + $(info ║ REPO_PROXY: $(REPO_PROXY)) # + $(info ║ ALPINELINUX_SRC_PREFIX: $(ALPINELINUX_SRC_PREFIX)) # http://mirrors.kernel.org/alpinelinux + $(info ║ ALPINELINUX_REL_VERSION: $(ALPINELINUX_REL_VERSION)) # + $(info ║ ALPINELINUX_MIRROR: $(ALPINELINUX_MIRROR)) # mirror.rackspace.com + $(info ╚═══════════════════════════════════════════════════════════════════════════════) +endif + +define bin_packages + $(shell cd $(ORIG_SRC) && \ + if [ 0`stat -c %Y $(OUTPUT_DIR)/$(notdir $(1)).list 2>/dev/null` -ge \ + 0`git log -1 --pretty=format:%ct` ]; then \ + cat $(OUTPUT_DIR)/$(notdir $(1)).list; \ + else \ + echo unknown.package; \ + fi) +endef + +### Targets required by Makefile.generic to build packages: + +# dist-prepare-chroot - initial preparation of chroot environment +# Specifically, load mounts for the build chroot +dist-prepare-chroot: $(CHROOT_DIR)/home/user/.prepared_base + @echo "--> Alpine linux dist-prepare-chroot (makefile):" + @mkdir -p "$(BUILDER_REPO_DIR)/pkgs" + @mkdir -p "$(CHROOT_DIR)/var/cache/apk" + @mkdir -p "$(CHROOT_DIR)/tmp/qubes-packages-mirror-repo" + +# Create the build chroot, if it does not already exist +$(CHROOT_DIR)/home/user/.prepared_base: $(ALPINELINUX_PLUGIN_DIR)/prepare-chroot-builder + @echo "--> Alpine linux preparing build chroot environment" + @sudo -E "$(ALPINELINUX_PLUGIN_DIR)/prepare-chroot-builder" "$(CHROOT_DIR)" $(DIST) || exit 1 + @touch "$(CHROOT_DIR)/home/user/.prepared_base" + +# dist-prep - some preparation of sources (if needed) +dist-prep: + @true + +# dist-build-dep - install build dependencies (should operate on chroot directory) +dist-build-dep: + @echo "--> Alpine linux dist-build-dep (makefile)" + @echo " --> Generate locales..." + @echo "en_US.UTF-8 UTF-8" | sudo tee -a $(CHROOT_DIR)/etc/locale.gen + @sudo $(CHROOT_ENV) chroot "$(CHROOT_DIR)" locale-gen + @echo "LANG=en_US.UTF-8" | sudo tee -a $(CHROOT_DIR)/etc/locale.conf + @sudo -E "$(ALPINELINUX_PLUGIN_DIR)/update-local-repo.sh" $(DIST) + +# dist-package - compile package (should operate on chroot directory) +# TODO: makepkg doesn't seem to honor $http_proxy +dist-package: + @echo "--> Alpine linux dist-package (makefile)" +ifndef PACKAGE + $(error "PACKAGE need to be set!") +endif + @echo " --> Building package in $(DIST_SRC)" + sudo $(CHROOT_ENV) /usr/sbin/chroot "$(CHROOT_DIR)" sh -c -l "abuild -r $(ABUILD_ARGS)" + +# dist-copy-out - copy compiled package out of chroot env; this target should +# move packages to ORIG_SRC (distro-specific subdir) and hardlink them to +# BUILDER_REPO_DIR +dist-copy-out: pkg_list_path = $(ORIG_SRC)/$(OUTPUT_DIR)/$(notdir $(PACKAGE)).list +dist-copy-out: + @echo "--> Archlinux dist-copy-out (makefile)" + @echo "--> Done:" >&3 + @set -e;\ + shopt -s nullglob;\ + mkdir -p $(ORIG_SRC)/$(OUTPUT_DIR);\ + echo -n > $(pkg_list_path);\ + for arch_chroot_dir in $(CHROOT_DIR)/$(DIST_SRC)/; do\ + arch_pkg_dir=$(ORIG_SRC)/$(OUTPUT_DIR);\ + mkdir -p $$arch_pkg_dir;\ + for pkg in $$arch_chroot_dir/*.pkg.tar.*; do\ + echo " $$arch_pkg_dir/`basename $$pkg`" >&3 ;\ + echo "$(OUTPUT_DIR)/`basename $$pkg`" >> $(pkg_list_path);\ + done;\ + mkdir -p $(BUILDER_REPO_DIR)/pkgs;\ + ln -f -t $(BUILDER_REPO_DIR)/pkgs $$arch_chroot_dir/*.pkg.tar.*;\ + done;\ + mv -t $$arch_pkg_dir $$arch_chroot_dir/*.pkg.tar.* + +### Additional targets + +# Sign packages +sign: sign_client = $(if $(GNUPG),$(GNUPG),gpg) +sign: + @if [ -d $(ORIG_SRC)/$(OUTPUT_DIR) ]; then \ + cd $(ORIG_SRC)/$(OUTPUT_DIR); \ + for filename in *.pkg.tar.zst; do\ + echo $$filename; \ + $(sign_client) --yes --local-user "$(ALPINELINUX_SIGN_KEY)" --detach-sign -o "$$filename.sig" "$$filename";\ + ln -f -t $(BUILDER_REPO_DIR)/pkgs "$$filename.sig";\ + done; \ + fi + + +# Copies requested packages (based on PACKAGE_SET, COMPONENT, DIST) to +# requested repository (UPDATE_REPO) +update-repo: +ifndef UPDATE_REPO + $(error "You need to specify destination repo in UPDATE_REPO variable") +endif +ifeq (,$(PACKAGE_LIST)) + @true +else +ifdef SNAPSHOT_FILE + @echo -n > $(SNAPSHOT_FILE) +endif + mkdir -p $(UPDATE_REPO)/pkgs; \ + for package in $(PACKAGE_LIST); do\ + pkgnames=`cat $(ORIG_SRC)/$(OUTPUT_DIR)/$$package.list`;\ + for pkgname in $$pkgnames; do\ + ln -f $(ORIG_SRC)/$$pkgname $(UPDATE_REPO)/pkgs/ || exit 1;\ + ln -f $(ORIG_SRC)/$$pkgname.sig $(UPDATE_REPO)/pkgs/ 2>/dev/null;\ + if [ -n "$(SNAPSHOT_FILE)" ]; then \ + echo $$pkgname >> "$(SNAPSHOT_FILE)"; \ + fi; \ + done; \ + done +endif + + +update-repo-from-snapshot: packages = $(shell cat $(SNAPSHOT_FILE) 2>/dev/null) +update-repo-from-snapshot: +ifndef UPDATE_REPO + $(error "You need to specify destination repo in UPDATE_REPO variable") +endif + mkdir -p $(UPDATE_REPO)/pkgs; \ + for f in $(packages); do \ + ln -f $(subst /$(TARGET_REPO)/,/$(SNAPSHOT_REPO)/,$(UPDATE_REPO)/)pkgs/`basename $$f` $(UPDATE_REPO)/pkgs/ || exit 1; \ + ln -f $(subst /$(TARGET_REPO)/,/$(SNAPSHOT_REPO)/,$(UPDATE_REPO)/)pkgs/`basename $$f`.sig $(UPDATE_REPO)/pkgs/ 2>/dev/null; \ + done + +check-repo: packages = $(foreach pkg,$(PACKAGE_LIST),$(call bin_packages,$(pkg))) +check-repo: +ifndef UPDATE_REPO + $(error "You need to specify destination repo in UPDATE_REPO variable") +endif + @if [ -n "$(strip $(packages))" ]; then \ + cd $(ORIG_SRC) && ls $(addprefix $(UPDATE_REPO)/pkgs/, $(notdir $(packages))) >/dev/null 2>&1 || exit 1; \ + else \ + echo "`tput bold`No packages defined by $(PACKAGE_LIST), syntax error?`tput sgr0`"; \ + exit 1; \ + fi diff --git a/Makefile.builder b/Makefile.builder new file mode 100644 index 0000000..0313c39 --- /dev/null +++ b/Makefile.builder @@ -0,0 +1,8 @@ +ifeq ($(DIST),alpinelinux) + ALPINELINUX_PLUGIN_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) + DISTRIBUTION := alpinelinux + BUILDER_MAKEFILE = $(ALPINELINUX_PLUGIN_DIR)Makefile.alpinelinux + TEMPLATE_SCRIPTS = $(ALPINELINUX_PLUGIN_DIR)scripts +endif + +# vim: ft=make diff --git a/prepare-chroot-base b/prepare-chroot-base new file mode 100755 index 0000000..0f6fdcc --- /dev/null +++ b/prepare-chroot-base @@ -0,0 +1,36 @@ +#!/bin/sh +# vim: set ts=4 sw=4 sts=4 et : +### prepare-chroot-base : Create a (any) chroot instance of Archlinux +### May be called from ./scripts/01_install_core.sh or ./prepare-chroot-archlinux +echo "--> Alpine linux prepare-chroot-base" + +INSTALLDIR="$1" +DISTRO="$2" # aka elsewhere as $DIST + +BOOTSTRAP_DIR="${CACHEDIR}/bootstrap" +ALPINELINUX_PLUGIN_DIR="${ALPINELINUX_PLUGIN_DIR:-"${SCRIPTSDIR}/.."}" +ALPINELINUX_VERSION=${ALPINELINUX_VERSION:-latest-stable} +ALPINELINUX_MIRROR=${ALPINELINUX_MIRROR:-http://dl-cdn.alpinelinux.org/alpine} + +set -e +if [ "$VERBOSE" -ge 2 ] || [ "$DEBUG" -gt 0 ]; then + set -x +fi + +exit_prepare() { + local exit_code=$? + echo " --> Unbinding INSTALLDIR..." + umount ${BOOTSTRAP_DIR}/mnt || true + exit $exit_code +} + +trap 'exit_prepare' 0 1 2 3 6 15 + +echo " --> Binding INSTALLDIR '${INSTALLDIR}' to bootstrap environment..." +mkdir -p "${BOOTSTRAP_DIR}/mnt" +mount --bind "$INSTALLDIR" "${BOOTSTRAP_DIR}/mnt" + +echo " --> Installing core apk packages..." +"$BOOTSTRAP_DIR"/sbin/apk.static -X $ALPINELINUX_MIRROR/$ALPINELINUX_VERSION/main -U --allow-untrusted -p "$INSTALLDIR" --initdb add alpine-base + +touch "${INSTALLDIR}/.prepared_base" diff --git a/prepare-chroot-builder b/prepare-chroot-builder new file mode 100755 index 0000000..d02ac2b --- /dev/null +++ b/prepare-chroot-builder @@ -0,0 +1,81 @@ +#!/bin/sh +# vim: set ts=4 sw=4 sts=4 et : + +### prepare-chroot-builder : Create the build chroot instance of Archlinux +### (in which to build Qubes packages) +echo "--> Alpine Linux prepare-chroot-builder" + +PLUGIN_DIR="$(dirname $0)" +INSTALLDIR="$1" +DISTRO="$2" + +SCRIPTSDIR=${ALPINELINUX_PLUGIN_DIR}scripts +export INSTALLDIR SCRIPTSDIR + +# do not make volatile private key dir for the package builds themselves +# this is interpreted by scripts/alpine-chroot +SKIP_VOLATILE_SECRET_KEY_DIR=true +export SKIP_VOLATILE_SECRET_KEY_DIR + +set -e +[ "$VERBOSE" -ge 1 -o "$DEBUG" -gt 0 ] && echo " --> INSTALLDIR: '$INSTALLDIR'" +[ "$VERBOSE" -ge 2 -o "$DEBUG" -gt 0 ] && set -x + +# /home/user will exist if we've completed the build previously +if ! [ -d "${INSTALLDIR}/home/user" ]; then + # It's non-existance means this is likely the initial run, so build it + + mkdir -p "$INSTALLDIR" + + echo " --> Installing alpine linux build root:" + "${PLUGIN_DIR}/prepare-chroot-base" "$INSTALLDIR" "$DISTRO" + + echo " --> Configure system accounts..." + [ -n "$SUDO_UID" ] && USER_OPTS="-u ${SUDO_UID}" + [ -n "$USER_UID" ] && USER_OPTS="-u ${USER_UID}" + if [ -n "$USER_GID" ]; then + chroot "$INSTALLDIR" /bin/addgroup -g "$USER_GID" user + elif [ -n "$SUDO_GID" ]; then + chroot "$INSTALLDIR" /bin/addgroup -g "$SUDO_GID" user + else + chroot "$INSTALLDIR" /bin/addgroup user + fi + chroot "$INSTALLDIR" /bin/sh -c \ + "useradd -g user -G wheel $USER_OPTS -m user; su -c 'mkdir qubes-src' - user" + + echo " --> Synchronize resolv.conf..." + cp /etc/resolv.conf "${INSTALLDIR}/etc/resolv.conf" + + # Checking for free disk free space doesn't work in chroots + # echo " --> Comment out CheckSpace in pacman.conf..." +# sed 's/^ *CheckSpace/#CheckSpace/g' -i "${INSTALLDIR}/etc/pacman.conf" + + echo " --> Installing required makepkg dependencies..." + pkgs="alpine-sdk" + "${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" /bin/sh -c \ + "http_proxy='${REPO_PROXY}' apk add $pkgs" + + # makepkg internally calls sudo without '-E', so we need to add an + # env_keep to honor proxy settings + echo " --> Configure sudo..." + cat > "${INSTALLDIR}/etc/sudoers.d/qubes-build-user" <> "${INSTALLDIR}/etc/pacman.conf" + # if [ "0$USE_QUBES_REPO_TESTING" -gt 0 ]; then + # cat "${ALPINELINUX_PLUGIN_DIR}repos/archlinux-qubes-repo-${USE_QUBES_REPO_VERSION}-current-testing.conf" \ + # >> "${INSTALLDIR}/etc/pacman.conf" + # fi + # "${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" pacman-key --add - < \ + # "${ALPINELINUX_PLUGIN_DIR}keys/qubes-repo-archlinux-key.asc" + # key_fpr=$(gpg --with-colons --show-key "${ALPINELINUX_PLUGIN_DIR}keys/qubes-repo-archlinux-key.asc" |\ + # grep ^fpr: | cut -d : -f 10) + #"${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" pacman-key --lsign "$key_fpr" + # fi +fi diff --git a/scripts/00_prepare.sh b/scripts/00_prepare.sh new file mode 100755 index 0000000..86765b5 --- /dev/null +++ b/scripts/00_prepare.sh @@ -0,0 +1,34 @@ +#! /bin/bash -- + +set -euo pipefail +echo "--> Alpine Linux 00_prepare.sh" + +if [[ -n "${REPO_PROXY+x}" ]]; then + export "https_proxy=$REPO_PROXY" "http_proxy=$REPO_PROXY" +fi +ALPINELINUX_PLUGIN_DIR="${ALPINELINUX_PLUGIN_DIR:-"${SCRIPTSDIR}/.."}" +ALPINELINUX_VERSION=${ALPINELINUX_VERSION:-latest-stable} +ALPINELINUX_MIRROR=${ALPINELINUX_MIRROR:-https://dl-cdn.alpinelinux.org/alpine} +ALPINELINUX_ARCH=${ALPINELINUX_ARCH:-x86_64} +APKTOOLS_VERSION=${APKTOOLS_VERSION:-2.12.7-r3} +APKTOOLS_FILE="${APKTOOLS_FILE:-apk-tools-static-"$APKTOOLS_VERSION".apk}" +APKTOOLS_URL="$ALPINELINUX_MIRROR/$ALPINELINUX_VERSION/main/$ALPINELINUX_ARCH/$APKTOOLS_FILE" + +[ "$VERBOSE" -ge 2 -o "$DEBUG" -gt 0 ] && set -x + +mkdir -p "${CACHEDIR}/pacman_cache" + +echo " --> Downloading Alpine Linux bootstrap (v${APKTOOLS_VERSION-})..." + +wget -N -P "$CACHEDIR" "$APKTOOLS_URL" + +if [ "${CACHEDIR}/${APKTOOLS_FILE}" -nt "${CACHEDIR}/bootstrap/.extracted" ]; then + echo " --> Extracting bootstrap tarball (nuking previous directory)..." + rm -rf "${CACHEDIR}/bootstrap/" + mkdir -p "${CACHEDIR}/bootstrap" + # By default will extract to a "root.x86_64" directory; strip that off + tar -xzC "${CACHEDIR}/bootstrap" -f "${CACHEDIR}/${APKTOOLS_FILE}" + touch "${CACHEDIR}/bootstrap/.extracted" +else + echo " --> NB: Bootstrap tarball not newer than bootstrap directory, will use existing!" +fi diff --git a/scripts/01_install_core.sh b/scripts/01_install_core.sh new file mode 100755 index 0000000..87aa8b4 --- /dev/null +++ b/scripts/01_install_core.sh @@ -0,0 +1,17 @@ +#!/bin/bash -e +# vim: set ts=4 sw=4 sts=4 et : +### 01_install_core.sh : Create build chroot install of Archlinux using pacstrap +echo "--> Alpine Linux 01_install_core.sh" + +ALPINELINUX_PLUGIN_DIR="${ALPINELINUX_PLUGIN_DIR:-"${SCRIPTSDIR}/.."}" +ALPINELINUX_VERSION=${ALPINELINUX_VERSION:-latest-stable} +ALPINELINUX_SRC_PREFIX="${ALPINELINUX_SRC_PREFIX:-https://dl-cdn.alpinelinux.org/alpine/"$ALPINELINUX_VERSION"/main}" + +set -e +[ "$VERBOSE" -ge 2 -o "$DEBUG" -gt 0 ] && set -x + +# make sure pacman master private key is _not_ stored in the TemplateVM - see +# scripts/alpine-chroot for details +unset SKIP_VOLATILE_SECRET_KEY_DIR + +"${ALPINELINUX_PLUGIN_DIR}/prepare-chroot-base" "$INSTALLDIR" "$DIST" diff --git a/scripts/04_install_qubes.sh b/scripts/04_install_qubes.sh new file mode 100755 index 0000000..e07d4ee --- /dev/null +++ b/scripts/04_install_qubes.sh @@ -0,0 +1,112 @@ +#!/bin/bash -e +# vim: set ts=4 sw=4 sts=4 et : +### 04_install_qubes.sh : Prepare chroot instance as a Qubes template +echo "--> Archlinux 04_install_qubes.sh" + +PACMAN_CACHE_DIR="${CACHEDIR}/pacman_cache" +PACMAN_CUSTOM_REPO_DIR="${PWD}/pkgs-for-template/${DIST}" +export PACMAN_CACHE_DIR PACMAN_CUSTOM_REPO_DIR "ALL_PROXY=$REPO_PROXY" + +set -e +if [ "$VERBOSE" -ge 2 ] || [ "$DEBUG" -gt 0 ]; then + set -x +fi + +echo " --> Enabling x86 repos..." +su -c "echo '[multilib]' >> $INSTALLDIR/etc/pacman.conf" +su -c "echo 'SigLevel = PackageRequired' >> $INSTALLDIR/etc/pacman.conf" +su -c "echo 'Include = /etc/pacman.d/mirrorlist' >> $INSTALLDIR/etc/pacman.conf" +sudo sed -Ei 's,^#(Server *= *https://mirrors\.kernel\.org/),\1,' "$INSTALLDIR/etc/pacman.d/mirrorlist" + +echo " --> Updating Qubes custom repository..." +# Repo Add need packages to be added in the right version number order as it only keeps the last entered package version +# shellcheck disable=SC2016 +"${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" /bin/sh -c \ + 'cd /tmp/qubes-packages-mirror-repo; for pkg in `ls -v pkgs/*.pkg.tar.zst`; do repo-add pkgs/qubes.db.tar.gz "$pkg"; done;' +chown -R --reference="$PACMAN_CUSTOM_REPO_DIR" "$PACMAN_CUSTOM_REPO_DIR" + +echo " --> Registering Qubes custom repository..." +# shellcheck disable=SC2016 +su -c 'echo "[qubes] " >> $INSTALLDIR/etc/pacman.conf' +# shellcheck disable=SC2016 +su -c 'echo "SigLevel = Never " >> $INSTALLDIR/etc/pacman.conf' +# shellcheck disable=SC2016 +su -c 'echo "Server = file:///tmp/qubes-packages-mirror-repo/pkgs " >> $INSTALLDIR/etc/pacman.conf' + +echo " --> Synchronize resolv.conf..." +cp /etc/resolv.conf "${INSTALLDIR}/etc/resolv.conf" + +echo " --> Updating pacman sources..." +"${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" /bin/sh -c \ + "until http_proxy='${REPO_PROXY}' pacman -Syu; do sleep 1; done" + +echo " --> Checking available qubes packages (for debugging only)..." +"${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" /bin/sh -c \ + "until http_proxy='${REPO_PROXY}' pacman -Ss qubes; do sleep 1; done" + +if [ -n "$USE_QUBES_REPO_VERSION" ]; then + # we don't check specific value here, assume correct branch of + # meta-packages component + echo " --> Installing repository qubes package..." + "${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" /bin/sh -c \ + "http_proxy='${REPO_PROXY}' pacman -S --noconfirm qubes-vm-repo" + if [ "0$USE_QUBES_REPO_TESTING" -gt 0 ]; then + echo " --> Enabling current-testing repository..." + ln -s "90-qubes-${USE_QUBES_REPO_VERSION}-current-testing.conf.disabled" \ + "$INSTALLDIR/etc/pacman.d/90-qubes-${USE_QUBES_REPO_VERSION}-current-testing.conf" + # abort if the file doesn't exist + if ! [ -f "$INSTALLDIR/etc/pacman.d/90-qubes-${USE_QUBES_REPO_VERSION}-current-testing.conf" ]; then + ls -l "$INSTALLDIR/etc/pacman.d/" + exit 1 + fi + fi + echo " --> Updating pacman sources..." + "${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" /bin/sh -c \ + "until http_proxy='${REPO_PROXY}' pacman -Syu; do sleep 1; done" +fi + +echo " --> Installing mandatory qubes packages..." +"${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" /bin/sh -c \ + "until http_proxy='${REPO_PROXY}' pacman -S --noconfirm qubes-vm-dependencies; do sleep 1; done" + +echo " --> Installing recommended qubes apps" +"${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" /bin/sh -c \ + "until http_proxy='${REPO_PROXY}' pacman -S --noconfirm qubes-vm-recommended; do sleep 1; done" + +echo " --> Updating template fstab file..." +cat >> "${INSTALLDIR}/etc/fstab" < Configuring system to our preferences..." +# Name network devices using simple names (ethX) +ln -s /dev/null "${INSTALLDIR}/etc/udev/rules.d/80-net-name-slot.rules" +# Enable some locales (incl. UTF-8) +sed 's/#en_US/en_US/g' -i "${INSTALLDIR}/etc/locale.gen" +"${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" locale-gen +echo 'LANG=en_US.UTF-8' > "${INSTALLDIR}/etc/locale.conf" + +# Creating a random file in /lib/modules to ensure that the directory in never deleted when packages are removed +mkdir -p "${INSTALLDIR}/lib/modules" +touch "${INSTALLDIR}/lib/modules/QUBES_NODELETE" + +# Remove qubes local repository definition +sed '/\[qubes]/,+2 d' -i "${INSTALLDIR}/etc/pacman.conf" diff --git a/scripts/09_cleanup.sh b/scripts/09_cleanup.sh new file mode 100755 index 0000000..3d1f965 --- /dev/null +++ b/scripts/09_cleanup.sh @@ -0,0 +1,40 @@ +#!/bin/bash -e +# vim: set ts=4 sw=4 sts=4 et : +### 09_cleanup.sh : Clean up the new chroot prior to image finalisation +echo "--> Archlinux 09_cleanup.sh" + +set -e +[ "$VERBOSE" -ge 2 -o "$DEBUG" -gt 0 ] && set -x + +# Remove unused packages and their dependencies (make dependencies) +cleanuppkgs="$("${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" /bin/sh -c 'pacman -Qdt | grep -v kernel | cut -d " " -f 1')" +if [ -n "$cleanuppkgs" ] ; then + echo " --> Packages that will be cleaned up: $cleanuppkgs" + "${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" /bin/sh -c "pacman --noconfirm -Rsc $cleanuppkgs" +else + echo " --> NB: No packages to clean up" +fi + +echo " --> Removing video plugins..." +videopkgs="$("${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" /bin/sh -c 'pacman -Qs -q xf86-video')" +echo $videopkgs | "${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" /bin/sh -c 'pacman --noconfirm -Rsc -' + +echo " --> Removing other font packages..." +"${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" /bin/sh -c \ + "pacman --noconfirm -Rsc xorg-fonts-100dpi xorg-fonts-75dpi" + +# TODO: Be more deliberate here; is the umount necessary? +# Moreover, given where this script is called, should we be bothering +# alpine-chroot? +echo " --> Cleaning up pacman state..." +umount "${INSTALLDIR}/var/cache/pacman" || true +unset PACMAN_CACHE_DIR +"${SCRIPTSDIR}/alpine-chroot" "$INSTALLDIR" /bin/sh -c \ + "pacman --noconfirm -Scc" + +echo " --> Cleaning /etc/resolv.conf" +rm -f "${INSTALLDIR}/etc/resolv.conf" +cat > "${INSTALLDIR}/etc/resolv.conf" << EOF +# This file intentionally left blank + +EOF diff --git a/scripts/alpine-chroot b/scripts/alpine-chroot new file mode 100755 index 0000000..89b3d9f --- /dev/null +++ b/scripts/alpine-chroot @@ -0,0 +1,144 @@ +#!/bin/bash +### +### A stripped-down version of the 'arch-chroot' script bundled with Archlinux +### +### This version drops unused code and makes a fey key modifications, +### annotated where they occur below. +### + +shopt -s extglob + +die() { error "$@"; exit 1; } + +chroot_add_mount() { + mount "$@" && CHROOT_ACTIVE_MOUNTS=("$2" "${CHROOT_ACTIVE_MOUNTS[@]}") +} + +setup_volatile_secret_key_dir() { + if [ "$SKIP_VOLATILE_SECRET_KEY_DIR" = "true" ]; then + return + fi + + # This directory stores secret GPG keys, so its contents must be kept secret + # at all costs. Anyone with access to the files in it can compromise the + # built TemplateVM and all VMs based on it. + secret_key_dir="$1/etc/pacman.d/gnupg/private-keys-v1.d" && + + # private-keys-v1.d does not exist before we create the tmpfs + mkdir -p -m 0755 -- "${secret_key_dir%/*}" && + mkdir -p -m 0000 -- "$secret_key_dir" && + + # Create README + [[ -f "$secret_key_dir/README" ]] || cat > "$secret_key_dir/README" <<'EOF' && +# Why is this directory immutable? + +In QubesOS, a TemplateVM’s root volume is readable by all AppVMs based on it. +Therefore, it cannot be used to store secret data. + +Pacman relies on the secrecy of its master key, which is normally stored in +`/etc/pacman.d/gnupg/private-keys-v1.d`. Anyone who has this key can sign +packages that Pacman will accept. Therefore, this key must not be stored on the +root volume. Furthermore, a user might (quite reasonably) assume that there is +no sensitive information on a TemplateVM’s private volume unless they have added +it explicitly. So the master key cannot be stored there either. + +The only remaining option is to use an ephemeral key that is only kept in +memory. That is what QubesOS does: during the build process, a ramfs is mounted +over /etc/pacman.d/gnupg/private-keys-v1.d, so that the secret key is kept in +memory. When the ramfs is unmounted, the key is destroyed along with it. + +There is one remaining problem: relying on a mount point is not fail-safe. If +the ramfs fails to mount, or if the user later runs operations like +`pacman-key --init`, a new master key will be generated. It will later be +leaked to AppVMs based on this template. + +To prevent this potentially disasterous failure, QubesOS marks the directory as +immutable. This ensures that nobody (not even root) can create any files in it. +When GPG tries to write its secret key to disk, it will fail, preventing any +leakage. + +P.S.: Why a ramfs and not a tmpfs? Data on a ramfs can never be paged out to +disk, which ensures that this key is never leaked to swap partitions. GPG +internally locks its memory into RAM to prevent similar problems. +EOF + # Mark private-keys-v1.d immutable, so that files (such as secret keys) + # cannot accidentally be created in it. + chattr -R +i -- "$secret_key_dir" && + + # See the README above for why this is a ramfs + chroot_add_mount pacman-privkeys "$secret_key_dir" -t ramfs -o mode=000,nosuid,noexec,nodev || exit +} + +chroot_setup() { + CHROOT_ACTIVE_MOUNTS=() + [[ $(trap -p EXIT) ]] && die '(BUG): attempting to overwrite existing EXIT trap' + trap 'chroot_teardown' EXIT + + # alpine-chroot drops the conditional bind mount on the chroot path, as + # it seemed to shadow mounts set up before arch-chroot was invoked + + # Set the correct permissions for mount points + chmod -- 0755 "$1/dev" "$1/run" && + chmod -- 0555 "$1/proc" "$1/sys" && + chmod -- 1777 "$1/tmp" && + + setup_volatile_secret_key_dir && + + chroot_add_mount proc "$1/proc" -t proc -o nosuid,noexec,nodev && + chroot_add_mount sys "$1/sys" -t sysfs -o nosuid,noexec,nodev,ro && + # alpine-chroot will never have occasion to use efivars, so don't bother + # mounting efivarfs here + chroot_add_mount udev "$1/dev" -t devtmpfs -o mode=0755,nosuid && + chroot_add_mount devpts "$1/dev/pts" -t devpts -o mode=0620,gid=5,nosuid,noexec && + chroot_add_mount shm "$1/dev/shm" -t tmpfs -o mode=1777,nosuid,nodev && + chroot_add_mount run "$1/run" -t tmpfs -o nosuid,nodev,mode=0755 && + chroot_add_mount tmp "$1/tmp" -t tmpfs -o mode=1777,strictatime,nodev,nosuid || + + exit + if [[ -d "$APKTOOLS_CACHE_DIR" ]]; then + APKTOOLS_CACHE_MOUNT_DIR="${APKTOOLS_CACHE_MOUNT_DIR:-$1/var/cache/apk}" + mkdir -p "$APKTOOLS_CACHE_MOUNT_DIR" + # Cached qubes packages may be from old runs and throw checksum errors + chroot_add_mount "$APKTOOLS_CACHE_DIR" "$APKTOOLS_CACHE_MOUNT_DIR" --bind + fi + if [[ -d "$APKTOOLS_CUSTOM_REPO_DIR" ]]; then + mkdir -p "$1/tmp/qubes-packages-mirror-repo" + chroot_add_mount "$APKTOOLS_CUSTOM_REPO_DIR" "$1/tmp/qubes-packages-mirror-repo" --bind + fi +} + +chroot_teardown() { + # alpine-chroot kills gpg-agent, started by pacman-key, which otherwise + # keeps the mounts busy and prevents unmounting + pkill gpg-agent + umount "${CHROOT_ACTIVE_MOUNTS[@]}" + unset CHROOT_ACTIVE_MOUNTS +} + +usage() { + cat <