From ed2f9b744d57f946e3bd257beaa275c74bcd2755 Mon Sep 17 00:00:00 2001 From: Adriaan de Groot Date: Mon, 18 Mar 2019 18:50:17 -0400 Subject: [PATCH] CI: AppImage support - Add a script for building a Calamares AppImage file (this is useful in *some* limited contexts, not a general way to distribute Calamares since it's much more efficient to use installed libs). - Add example config files for this AppImage build. - Download the linuxdeploy tools if they are missing - Document script flags some more --- ci/AppImage.md | 45 ++++ ci/AppImage.sh | 227 ++++++++++++++++++ .../branding/default/squid.png | Bin 0 -> 10796 bytes .../modules/displaymanager.conf | 28 +++ data/config-appimage/modules/finished.conf | 21 ++ data/config-appimage/modules/keyboard.conf | 16 ++ data/config-appimage/modules/locale.conf | 31 +++ data/config-appimage/modules/users.conf | 62 +++++ data/config-appimage/modules/welcome.conf | 46 ++++ data/config-appimage/settings.conf | 35 +++ 10 files changed, 511 insertions(+) create mode 100644 ci/AppImage.md create mode 100644 ci/AppImage.sh create mode 100644 data/config-appimage/branding/default/squid.png create mode 100644 data/config-appimage/modules/displaymanager.conf create mode 100644 data/config-appimage/modules/finished.conf create mode 100644 data/config-appimage/modules/keyboard.conf create mode 100644 data/config-appimage/modules/locale.conf create mode 100644 data/config-appimage/modules/users.conf create mode 100644 data/config-appimage/modules/welcome.conf create mode 100644 data/config-appimage/settings.conf diff --git a/ci/AppImage.md b/ci/AppImage.md new file mode 100644 index 000000000..7fa51a8bc --- /dev/null +++ b/ci/AppImage.md @@ -0,0 +1,45 @@ +# AppImage building for Calamares + +> It is possible to build Calamares as an AppImage (perhaps other +> containerized formats as well). This might make sense for +> OEM phase-1 deployments in environments where Calamares is +> not using the native toolkit. + +## AppImage tools + +You will need + - [`linuxdeploy-x86_64.AppImage`](https://github.com/linuxdeploy/linuxdeploy/releases) + - [`linuxdeploy-plugin-qt-x86_64.AppImage`](https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases) + - [`linuxdeploy-plugin-conda.sh`](https://github.com/linuxdeploy/linuxdeploy-plugin-conda) + +These tools should run -- they are bundled as AppImages after all -- on +any modern Linux system. The [AppImage packaging documentation](https://docs.appimage.org/packaging-guide/) +explains how the whole tooling works. + +If the tools are not present, the build script (see below) will download them, +but you should save them for later. + +## AppImage build + +From the **source** directory, run `ci/AppImage.sh`: + - Use `--tools-dir` to copy the tools from a local cache rather than + downloading them again. + - Run it with `--cmake-args` for special CMake handling. + - Use `--skip-build` to avoid rebuilding Calamares all the time. + - Use `--config-dir` to copy in Calamares configuration files (e.g. + *settings.conf* and the module configuration files) from a given + directory. + +The build process will: + - copy (or download) the AppImage tools into a fresh build directory + - configure and build Calamares with suitable settings + - modifies the standard `.desktop` file to be AppImage-compatible + - builds the image with the AppImage tools + +## AppImage caveats + +The resulting AppImage, `Calamares-x86_64.AppImage`, can be run as if it is +a regular Calamares executable. For internal reasons it always passes the +`-X` flag; any other command-line flags are passed in unchanged. Internally, +`XDG_*_DIRS` are used to get Calamares to find the resources inside the AppImage +rather than in the host system. diff --git a/ci/AppImage.sh b/ci/AppImage.sh new file mode 100644 index 000000000..7dccc4d89 --- /dev/null +++ b/ci/AppImage.sh @@ -0,0 +1,227 @@ +#! /bin/sh +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright 2019 Adriaan de Groot +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +### END LICENSES + +### USAGE +# +# Shell script to help build an AppImage for Calamares. +# +# Usage: +# AppImage.sh [-T|--tools-dir ] +# [-C|--cmake-args ] +# [-c|--config-dir ] +# [-s|--skip-build] +# +# Multiple --cmake-args arguments will be collected together and passed to +# CMake before building the application. +# +# Use --tools-dir to indicate where the linuxdeploy tools are located. +# +# Use --config to copy a config-directory (with settings.conf and others) +# into the resulting image, +# +### END USAGE + +TOOLS_DIR="." +CMAKE_ARGS="" +NO_SKIP_BUILD="true" +CONFIG_DIR="" +while test "$#" -gt 0 +do + case "x$1" in + x--help|x-h) + sed -e '1,/USAGE/d' -e '/END.USAGE/,$d' < "$0" + return 0 + ;; + x--tools-dir|x-T) + TOOLS_DIR="$2" + shift + ;; + x--cmake-args|x-C) + CMAKE_ARGS="$CMAKE_ARGS $2" + shift + ;; + x--config-dir|x-c) + CONFIG_DIR="$2" + shift + ;; + x--skip-build|x-s) + NO_SKIP_BUILD="false" + ;; + *) + echo "! Unknown argument '$1'." + exit 1 + ;; + esac + test "$#" -gt 0 || { echo "! Missing arguments."; exit 1; } + shift +done + +### Check where we're running +# +BIN_DIR=$( cd $( dirname "$0" ) && pwd -P ) +test -d "$BIN_DIR" || { echo "! Could not find BIN_DIR"; exit 1; } +test -f "$BIN_DIR/AppImage.sh" || { echo "! $BIN_DIR does not have AppImage.sh"; exit 1; } + +SRC_DIR=$( cd "$BIN_DIR/.." && pwd -P ) +test -d "$SRC_DIR" || { echo "! Could not find SRC_DIR"; exit 1; } +test -d "$SRC_DIR/ci" || { echo "! $SRC_DIR isn't a top-level Calamares checkout"; exit 1; } +test -f "$SRC_DIR/CMakeLists.txt" || { echo "! SRC_DIR is missing CMakeLists.txt"; exit 1; } + +### Check pre-requisites +# +BUILD_DIR=build-AppImage +test -d "$BUILD_DIR" || mkdir -p "$BUILD_DIR" +test -d "$BUILD_DIR" || { echo "! Could not create $BUILD_DIR"; exit 1; } + +for tool in linuxdeploy-x86_64.AppImage linuxdeploy-plugin-qt-x86_64.AppImage linuxdeploy-plugin-conda.sh +do + if test -x "$BUILD_DIR/$tool" ; then + # This tool is ok + : + else + if test -f "$TOOLS_DIR/$tool" ; then + cp "$TOOLS_DIR/$tool" "$BUILD_DIR/$tool" || exit 1 + else + fetch=$( grep "^# URL .*$tool\$" "$0" | sed 's/# URL *//' ) + curl -L -o "$BUILD_DIR/$tool" "$fetch" + fi + chmod +x "$BUILD_DIR/$tool" + test -x "$BUILD_DIR/$tool" || { echo "! Missing tool $tool in tools-dir $TOOLS_DIR"; exit 1; } + fi +done + +if test -z "$CONFIG_DIR" ; then + echo "# Using basic settings.conf" +else + test -f "$CONFIG_DIR/settings.conf" || { echo "! No settings.conf in $CONFIG_DIR"; exit 1; } +fi + +### Clean up build-directory +# +rm -rf "$BUILD_DIR/AppDir" +if $NO_SKIP_BUILD ; then + rm -rf "$BUILD_DIR/build" + mkdir "$BUILD_DIR/build" || { echo "! Could not create $BUILD_DIR/build for the cmake-build."; exit 1; } +else + test -d "$BUILD_DIR/build" || { echo "! No build found in $BUILD_DIR, but --skip-build is given."; exit 1; } + test -x "$BUILD_DIR/build/calamares" || { echo "! No complete build found in $BUILD_DIR/build ."; exit 1; } +fi +mkdir "$BUILD_DIR/AppDir" || { echo "! Could not create $BUILD_DIR/AppDir for the AppImage install."; exit 1; } +LOG_FILE="$BUILD_DIR/AppImage.log" +rm -f "$LOG_FILE" + +### Build Calamares +# +if $NO_SKIP_BUILD ; then + echo "# Running cmake ..." + ( + cd "$BUILD_DIR/build" && + cmake "$SRC_DIR" -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib $CMAKE_ARGS + ) > "$LOG_FILE" 2>&1 || { tail -10 "$LOG_FILE" ; echo "! Could not run CMake"; exit 1; } + echo "# Running make ..." + ( + cd "$BUILD_DIR/build" && + make -j4 + ) > "$LOG_FILE" 2>&1 || { tail -10 "$LOG_FILE" ; echo "! Could not run make"; exit 1; } +fi +echo "# Running make install ..." +( + cd "$BUILD_DIR/build" && + make install DESTDIR=../AppDir +) > "$LOG_FILE" 2>&1 || { tail -10 "$LOG_FILE" ; echo "! Could not run make install"; exit 1; } + +### Modify installation +# +IMAGE_DIR="$BUILD_DIR/AppDir" + +# Munge the desktop file to not use absolute paths or pkexec +sed -i \ + -e 's+^Exec=.*+Exec=calamares+' \ + -e 's+^Name=.*+Name=Calamares+' \ + "$IMAGE_DIR"/usr/share/applications/calamares.desktop + +# Replace the executable with a shell-proxy +test -x "$IMAGE_DIR/usr/bin/calamares" || { echo "! Does not seem to have installed calamares"; exit 1; } +mv "$IMAGE_DIR/usr/bin/calamares" "$IMAGE_DIR/usr/bin/calamares.bin" +cat > "$IMAGE_DIR/usr/bin/calamares" <<"EOF" +#! /bin/sh +# +# Calamares proxy-script +export XDG_DATA_DIRS="$APPDIR/usr/share/calamares:" +export XDG_CONFIG_DIRS="$APPDIR/etc/calamares:$D/usr/share:" +cd "$APPDIR" +exec "$APPDIR"/usr/bin/calamares.bin -X "$@" +EOF +chmod 755 "$IMAGE_DIR/usr/bin/calamares" +test -x "$IMAGE_DIR/usr/bin/calamares" || { echo "! Does not seem to have proxy for calamares"; exit 1; } + +### Install additional files +# +PLUGIN_DIR=$( qmake -query QT_INSTALL_PLUGINS ) +for plugin in \ + libpmsfdiskbackendplugin.so \ + libpmdummybackendplugin.so +do + cp "$PLUGIN_DIR/$plugin" "$IMAGE_DIR/usr/lib" || { echo "! Could not copy plugin $plugin"; exit 1; } +done + +# Install configuration files +ETC_DIR="$IMAGE_DIR"/etc/calamares +mkdir -p "$ETC_DIR" +test -d "$ETC_DIR" || { echo "! Could not create /etc/calamares in image."; exit 1; } + +if test -z "$CONFIG_DIR" ; then + echo "# Using basic settings.conf" + cp "$SRC_DIR/settings.conf" "$ETC_DIR" +else + test -f "$CONFIG_DIR/settings.conf" || { echo "! No settings.conf in $CONFIG_DIR"; exit 1; } + mkdir -p "$ETC_DIR/modules" + cp "$CONFIG_DIR/settings.conf" "$ETC_DIR" + test -d "$CONFIG_DIR/modules" && cp -r "$CONFIG_DIR/modules" "$ETC_DIR" + test -d "$CONFIG_DIR/branding" && cp -r "$CONFIG_DIR/branding" "$IMAGE_DIR/usr/share/calamares" +fi + +### Build the AppImage +# +# +echo "# Building AppImage" +( + export QT_SELECT=qt5 # Otherwise might pick Qt4 in image + export LD_LIBRARY_PATH=AppDir/usr/lib # RPATH isn't set in the executable + cd "$BUILD_DIR" && + ./linuxdeploy-x86_64.AppImage --appdir=AppDir/ --plugin=qt --output=appimage +) > "$LOG_FILE" 2>&1 || { tail -10 "$LOG_FILE" ; echo "! Could not create image"; exit 1; } + +exit 0 +### Database for installation +# +# URL https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage +# URL https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage +# URL https://raw.githubusercontent.com/TheAssassin/linuxdeploy-plugin-conda/master/linuxdeploy-plugin-conda.sh diff --git a/data/config-appimage/branding/default/squid.png b/data/config-appimage/branding/default/squid.png new file mode 100644 index 0000000000000000000000000000000000000000..dbe615c18a3c5e55837fb5fb35ab04cb5180cd37 GIT binary patch literal 10796 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