mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
e9754efbbe
The recent macOS security patch renders our codesigning "fix" of setting the sandbox entitlement to false twice unusable. This patch adds a full provisioning profile and adjusts the signing procedure to not include entitlements for Qt frameworks. The patch also changes the app and bundle ID, so granted accessibility privileges have to be granted again after installing the update. Fixes #4398 Fixes #4515
1330 lines
43 KiB
Bash
Executable File
1330 lines
43 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# KeePassXC Release Preparation Helper
|
|
# Copyright (C) 2017 KeePassXC team <https://keepassxc.org/>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 2 or (at your option)
|
|
# version 3 of the License.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
printf "\e[1m\e[32mKeePassXC\e[0m Release Preparation Helper\n"
|
|
printf "Copyright (C) 2017 KeePassXC Team <https://keepassxc.org/>\n\n"
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
# global default values
|
|
# -----------------------------------------------------------------------
|
|
RELEASE_NAME=""
|
|
APP_NAME="KeePassXC"
|
|
SRC_DIR="."
|
|
GPG_KEY="CFB4C2166397D0D2"
|
|
GPG_GIT_KEY=""
|
|
OUTPUT_DIR="release"
|
|
SOURCE_BRANCH=""
|
|
TARGET_BRANCH="master"
|
|
TAG_NAME=""
|
|
DOCKER_IMAGE=""
|
|
DOCKER_CONTAINER_NAME="keepassxc-build-container"
|
|
CMAKE_OPTIONS=""
|
|
CPACK_GENERATORS="WIX;ZIP"
|
|
COMPILER="g++"
|
|
MAKE_OPTIONS="-j8"
|
|
BUILD_PLUGINS="all"
|
|
INSTALL_PREFIX="/usr/local"
|
|
ORIG_BRANCH=""
|
|
ORIG_CWD="$(pwd)"
|
|
MACOSX_DEPLOYMENT_TARGET=10.12
|
|
GREP="grep"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# helper functions
|
|
# -----------------------------------------------------------------------
|
|
printUsage() {
|
|
local cmd
|
|
if [ "" == "$1" ] || [ "help" == "$1" ]; then
|
|
cmd="COMMAND"
|
|
elif [ "check" == "$1" ] || [ "merge" == "$1" ] || [ "build" == "$1" ] \
|
|
|| [ "gpgsign" == "$1" ] || [ "appsign" == "$1" ] || [ "appimage" == "$1" ]; then
|
|
cmd="$1"
|
|
else
|
|
logError "Unknown command: '$1'\n"
|
|
cmd="COMMAND"
|
|
fi
|
|
|
|
printf "\e[1mUsage:\e[0m $(basename $0) $cmd [options]\n"
|
|
|
|
if [ "COMMAND" == "$cmd" ]; then
|
|
cat << EOF
|
|
|
|
Commands:
|
|
check Perform a dry-run check, nothing is changed
|
|
merge Merge release branch into main branch and create release tags
|
|
build Build and package binary release from sources
|
|
gpgsign Sign previously compiled release packages with GPG
|
|
appsign Sign binaries with code signing certificates on Windows and macOS
|
|
help Show help for the given command
|
|
EOF
|
|
elif [ "merge" == "$cmd" ]; then
|
|
cat << EOF
|
|
|
|
Merge release branch into main branch and create release tags
|
|
|
|
Options:
|
|
-v, --version Release version number or name (required)
|
|
-a, --app-name Application name (default: '${APP_NAME}')
|
|
-s, --source-dir Source directory (default: '${SRC_DIR}')
|
|
-k, --key GPG key used to sign the merge commit and release tag,
|
|
leave empty to let Git choose your default key
|
|
(default: '${GPG_GIT_KEY}')
|
|
-r, --release-branch Source release branch to merge from (default: 'release/VERSION')
|
|
--target-branch Target branch to merge to (default: '${TARGET_BRANCH}')
|
|
-t, --tag-name Override release tag name (defaults to version number)
|
|
-h, --help Show this help
|
|
EOF
|
|
elif [ "build" == "$cmd" ]; then
|
|
cat << EOF
|
|
|
|
Build and package binary release from sources
|
|
|
|
Options:
|
|
-v, --version Release version number or name (required)
|
|
-a, --app-name Application name (default: '${APP_NAME}')
|
|
-s, --source-dir Source directory (default: '${SRC_DIR}')
|
|
-o, --output-dir Output directory where to build the release
|
|
(default: '${OUTPUT_DIR}')
|
|
-t, --tag-name Release tag to check out (defaults to version number)
|
|
-b, --build Build sources after exporting release
|
|
-d, --docker-image Use the specified Docker image to compile the application.
|
|
The image must have all required build dependencies installed.
|
|
This option has no effect if --build is not set.
|
|
--container-name Docker container name (default: '${DOCKER_CONTAINER_NAME}')
|
|
The container must not exist already
|
|
--snapcraft Create and use docker image to build snapcraft distribution.
|
|
This option has no effect if --docker-image is not set.
|
|
--appimage Build a Linux AppImage after compilation.
|
|
If this option is set, --install-prefix has no effect
|
|
--appsign Perform platform specific App Signing before packaging
|
|
-k, --key Specify the App Signing Key/Identity
|
|
-c, --cmake-options Additional CMake options for compiling the sources
|
|
--compiler Compiler to use (default: '${COMPILER}')
|
|
-m, --make-options Make options for compiling sources (default: '${MAKE_OPTIONS}')
|
|
-g, --generators Additional CPack generators (default: '${CPACK_GENERATORS}')
|
|
-i, --install-prefix Install prefix (default: '${INSTALL_PREFIX}')
|
|
-p, --plugins Space-separated list of plugins to build
|
|
(default: ${BUILD_PLUGINS})
|
|
--snapshot Don't checkout the release tag
|
|
-n, --no-source-tarball Don't build source tarball
|
|
-h, --help Show this help
|
|
EOF
|
|
elif [ "gpgsign" == "$cmd" ]; then
|
|
cat << EOF
|
|
|
|
Sign previously compiled release packages with GPG
|
|
|
|
Options:
|
|
-f, --files Files to sign (required)
|
|
-k, --key GPG key used to sign the files (default: '${GPG_KEY}')
|
|
-h, --help Show this help
|
|
EOF
|
|
elif [ "appsign" == "$cmd" ]; then
|
|
cat << EOF
|
|
|
|
Sign binaries with code signing certificates on Windows and macOS
|
|
|
|
Options:
|
|
-f, --files Files to sign (required)
|
|
-k, --key, -i, --identity
|
|
Signing Key or Apple Developer ID (required)
|
|
-u, --username Apple username for notarization (required on macOS)
|
|
-c, --keychain Apple keychain entry name storing the notarization
|
|
app password (default: 'AC_PASSWORD')
|
|
-h, --help Show this help
|
|
EOF
|
|
elif [ "appimage" == "$cmd" ]; then
|
|
cat << EOF
|
|
|
|
Generate Linux AppImage from 'make install' AppDir
|
|
|
|
Options:
|
|
-a, --appdir Input AppDir (required)
|
|
-v, --version KeePassXC version
|
|
-o, --output-dir Output directory where to build the AppImage
|
|
(default: '${OUTPUT_DIR}')
|
|
-d, --docker-image Use the specified Docker image to build the AppImage.
|
|
The image must have all required build dependencies installed.
|
|
--container-name Docker container name (default: '${DOCKER_CONTAINER_NAME}')
|
|
The container must not exist already
|
|
--appsign Embed a PGP signature into the AppImage
|
|
-k, --key The PGP Signing Key
|
|
--verbosity linuxdeploy verbosity (default: 3)
|
|
-h, --help Show this help
|
|
EOF
|
|
fi
|
|
}
|
|
|
|
logInfo() {
|
|
printf "\e[1m[ \e[34mINFO\e[39m ]\e[0m $1\n"
|
|
}
|
|
|
|
logWarn() {
|
|
printf "\e[1m[ \e[33mWARNING\e[39m ]\e[0m $1\n"
|
|
}
|
|
|
|
logError() {
|
|
printf "\e[1m[ \e[31mERROR\e[39m ]\e[0m $1\n" >&2
|
|
}
|
|
|
|
init() {
|
|
if [ "" == "$RELEASE_NAME" ]; then
|
|
logError "Missing arguments, --version is required!\n"
|
|
printUsage "check"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "" == "$TAG_NAME" ]; then
|
|
TAG_NAME="$RELEASE_NAME"
|
|
fi
|
|
|
|
if [ "" == "$SOURCE_BRANCH" ]; then
|
|
SOURCE_BRANCH="release/${RELEASE_NAME}"
|
|
fi
|
|
|
|
ORIG_CWD="$(pwd)"
|
|
SRC_DIR="$(realpath "$SRC_DIR")"
|
|
cd "$SRC_DIR" > /dev/null 2>&1
|
|
ORIG_BRANCH="$(git rev-parse --abbrev-ref HEAD 2> /dev/null)"
|
|
cd "$ORIG_CWD"
|
|
}
|
|
|
|
cleanup() {
|
|
logInfo "Checking out original branch..."
|
|
if [ "" != "$ORIG_BRANCH" ]; then
|
|
git checkout "$ORIG_BRANCH" > /dev/null 2>&1
|
|
fi
|
|
logInfo "Leaving source directory..."
|
|
cd "$ORIG_CWD"
|
|
}
|
|
|
|
exitError() {
|
|
logError "$1"
|
|
cleanup
|
|
exit 1
|
|
}
|
|
|
|
exitTrap() {
|
|
exitError "Existing upon user request..."
|
|
}
|
|
|
|
cmdExists() {
|
|
command -v "$1" &> /dev/null
|
|
}
|
|
|
|
checkGrepCompat() {
|
|
if ! grep -qPzo test <(echo test) 2> /dev/null; then
|
|
if [ -e /usr/local/opt/grep/libexec/gnubin/grep ]; then
|
|
GREP="/usr/local/opt/grep/libexec/gnubin/grep"
|
|
else
|
|
exitError "Incompatible grep implementation! If on macOS, please run 'brew install grep'."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
checkSourceDirExists() {
|
|
if [ ! -d "$SRC_DIR" ]; then
|
|
exitError "Source directory '${SRC_DIR}' does not exist!"
|
|
fi
|
|
}
|
|
|
|
checkOutputDirDoesNotExist() {
|
|
if [ -e "$OUTPUT_DIR" ]; then
|
|
exitError "Output directory '$OUTPUT_DIR' already exists. Please choose a different location!"
|
|
fi
|
|
}
|
|
|
|
checkGitRepository() {
|
|
if [ ! -d .git ] || [ ! -f CHANGELOG.md ]; then
|
|
exitError "Source directory is not a valid Git repository!"
|
|
fi
|
|
}
|
|
|
|
checkReleaseDoesNotExist() {
|
|
git tag | $GREP -q "^$TAG_NAME$"
|
|
if [ $? -eq 0 ]; then
|
|
exitError "Release '$RELEASE_NAME' (tag: '$TAG_NAME') already exists!"
|
|
fi
|
|
}
|
|
|
|
checkWorkingTreeClean() {
|
|
git diff-index --quiet HEAD --
|
|
if [ $? -ne 0 ]; then
|
|
exitError "Current working tree is not clean! Please commit or unstage any changes."
|
|
fi
|
|
}
|
|
|
|
checkSourceBranchExists() {
|
|
git rev-parse "$SOURCE_BRANCH" > /dev/null 2>&1
|
|
if [ $? -ne 0 ]; then
|
|
exitError "Source branch '$SOURCE_BRANCH' does not exist!"
|
|
fi
|
|
}
|
|
|
|
checkTargetBranchExists() {
|
|
git rev-parse "$TARGET_BRANCH" > /dev/null 2>&1
|
|
if [ $? -ne 0 ]; then
|
|
exitError "Target branch '$TARGET_BRANCH' does not exist!"
|
|
fi
|
|
}
|
|
|
|
checkVersionInCMake() {
|
|
local app_name_upper="$(echo "$APP_NAME" | tr '[:lower:]' '[:upper:]')"
|
|
local major_num="$(echo ${RELEASE_NAME} | cut -f1 -d.)"
|
|
local minor_num="$(echo ${RELEASE_NAME} | cut -f2 -d.)"
|
|
local patch_num="$(echo ${RELEASE_NAME} | cut -f3 -d. | cut -f1 -d-)"
|
|
|
|
$GREP -q "${app_name_upper}_VERSION_MAJOR \"${major_num}\"" CMakeLists.txt
|
|
if [ $? -ne 0 ]; then
|
|
exitError "${app_name_upper}_VERSION_MAJOR not updated to '${major_num}' in CMakeLists.txt!"
|
|
fi
|
|
|
|
$GREP -q "${app_name_upper}_VERSION_MINOR \"${minor_num}\"" CMakeLists.txt
|
|
if [ $? -ne 0 ]; then
|
|
exitError "${app_name_upper}_VERSION_MINOR not updated to '${minor_num}' in CMakeLists.txt!"
|
|
fi
|
|
|
|
$GREP -q "${app_name_upper}_VERSION_PATCH \"${patch_num}\"" CMakeLists.txt
|
|
if [ $? -ne 0 ]; then
|
|
exitError "${app_name_upper}_VERSION_PATCH not updated to '${patch_num}' in CMakeLists.txt!"
|
|
fi
|
|
}
|
|
|
|
checkChangeLog() {
|
|
if [ ! -f CHANGELOG.md ]; then
|
|
exitError "No CHANGELOG file found!"
|
|
fi
|
|
|
|
$GREP -qPzo "## ${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n" CHANGELOG.md
|
|
if [ $? -ne 0 ]; then
|
|
exitError "'CHANGELOG.md' has not been updated to the '${RELEASE_NAME}' release!"
|
|
fi
|
|
}
|
|
|
|
checkAppStreamInfo() {
|
|
if [ ! -f share/linux/org.keepassxc.KeePassXC.appdata.xml ]; then
|
|
exitError "No AppStream info file found!"
|
|
fi
|
|
|
|
$GREP -qPzo "<release version=\"${RELEASE_NAME}\" date=\"\d{4}-\d{2}-\d{2}\">" share/linux/org.keepassxc.KeePassXC.appdata.xml
|
|
if [ $? -ne 0 ]; then
|
|
exitError "'share/linux/org.keepassxc.KeePassXC.appdata.xml' has not been updated to the '${RELEASE_NAME}' release!"
|
|
fi
|
|
}
|
|
|
|
checkSnapcraft() {
|
|
if [ ! -f snap/snapcraft.yaml ]; then
|
|
echo "Could not find snap/snapcraft.yaml!"
|
|
return
|
|
fi
|
|
|
|
$GREP -qPzo "version: ${RELEASE_NAME}" snap/snapcraft.yaml
|
|
if [ $? -ne 0 ]; then
|
|
exitError "'snapcraft.yaml' has not been updated to the '${RELEASE_NAME}' release!"
|
|
fi
|
|
|
|
$GREP -qPzo "KEEPASSXC_BUILD_TYPE=Release" snap/snapcraft.yaml
|
|
if [ $? -ne 0 ]; then
|
|
exitError "'snapcraft.yaml' is not set for a release build!"
|
|
fi
|
|
}
|
|
|
|
checkTransifexCommandExists() {
|
|
if ! cmdExists tx; then
|
|
exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'."
|
|
fi
|
|
}
|
|
|
|
checkSigntoolCommandExists() {
|
|
if ! cmdExists signtool; then
|
|
exitError "signtool command not found on the PATH! Add the Windows SDK binary folder to your PATH."
|
|
fi
|
|
}
|
|
|
|
checkXcodeSetup() {
|
|
if ! cmdExists xcrun; then
|
|
exitError "xcrun command not found on the PATH! Please check that you have correctly installed Xcode."
|
|
fi
|
|
if ! xcrun -f codesign > /dev/null 2>&1; then
|
|
exitError "codesign command not found. You may need to run 'sudo xcode-select -r' to set up Xcode."
|
|
fi
|
|
if ! xcrun -f altool > /dev/null 2>&1; then
|
|
exitError "altool command not found. You may need to run 'sudo xcode-select -r' to set up Xcode."
|
|
fi
|
|
if ! xcrun -f stapler > /dev/null 2>&1; then
|
|
exitError "stapler command not found. You may need to run 'sudo xcode-select -r' to set up Xcode."
|
|
fi
|
|
}
|
|
|
|
checkQt5LUpdateExists() {
|
|
if cmdExists lupdate && ! $(lupdate -version | $GREP -q "lupdate version 5\."); then
|
|
if ! cmdExists lupdate-qt5; then
|
|
exitError "Qt Linguist tool (lupdate-qt5) is not installed! Please install using 'apt install qttools5-dev-tools'"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
performChecks() {
|
|
logInfo "Performing basic checks..."
|
|
|
|
checkGrepCompat
|
|
|
|
checkSourceDirExists
|
|
|
|
logInfo "Changing to source directory..."
|
|
cd "${SRC_DIR}"
|
|
|
|
logInfo "Validating toolset and repository..."
|
|
|
|
checkTransifexCommandExists
|
|
checkQt5LUpdateExists
|
|
checkGitRepository
|
|
checkReleaseDoesNotExist
|
|
checkWorkingTreeClean
|
|
checkSourceBranchExists
|
|
checkTargetBranchExists
|
|
|
|
logInfo "Checking out '${SOURCE_BRANCH}'..."
|
|
git checkout "$SOURCE_BRANCH"
|
|
|
|
logInfo "Attempting to find '${RELEASE_NAME}' in various files..."
|
|
|
|
checkVersionInCMake
|
|
checkChangeLog
|
|
checkAppStreamInfo
|
|
checkSnapcraft
|
|
|
|
logInfo "\e[1m\e[32mAll checks passed!\e[0m"
|
|
}
|
|
|
|
# re-implement realpath for OS X (thanks mschrag)
|
|
# https://superuser.com/questions/205127/
|
|
if ! cmdExists realpath; then
|
|
realpath() {
|
|
pushd . > /dev/null
|
|
if [ -d "$1" ]; then
|
|
cd "$1"
|
|
dirs -l +0
|
|
else
|
|
cd "$(dirname "$1")"
|
|
cur_dir=$(dirs -l +0)
|
|
|
|
if [ "$cur_dir" == "/" ]; then
|
|
echo "$cur_dir$(basename "$1")"
|
|
else
|
|
echo "$cur_dir/$(basename "$1")"
|
|
fi
|
|
fi
|
|
popd > /dev/null
|
|
}
|
|
fi
|
|
|
|
|
|
trap exitTrap SIGINT SIGTERM
|
|
|
|
# -----------------------------------------------------------------------
|
|
# check command
|
|
# -----------------------------------------------------------------------
|
|
check() {
|
|
while [ $# -ge 1 ]; do
|
|
local arg="$1"
|
|
case "$arg" in
|
|
-v|--version)
|
|
RELEASE_NAME="$2"
|
|
shift ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
init
|
|
|
|
performChecks
|
|
|
|
cleanup
|
|
|
|
logInfo "Congrats! You can successfully merge, build, and sign KeepassXC."
|
|
}
|
|
|
|
# -----------------------------------------------------------------------
|
|
# merge command
|
|
# -----------------------------------------------------------------------
|
|
merge() {
|
|
while [ $# -ge 1 ]; do
|
|
local arg="$1"
|
|
case "$arg" in
|
|
-v|--version)
|
|
RELEASE_NAME="$2"
|
|
shift ;;
|
|
|
|
-a|--app-name)
|
|
APP_NAME="$2"
|
|
shift ;;
|
|
|
|
-s|--source-dir)
|
|
SRC_DIR="$2"
|
|
shift ;;
|
|
|
|
-k|--key|-g|--gpg-key)
|
|
GPG_GIT_KEY="$2"
|
|
shift ;;
|
|
|
|
-r|--release-branch)
|
|
SOURCE_BRANCH="$2"
|
|
shift ;;
|
|
|
|
--target-branch)
|
|
TARGET_BRANCH="$2"
|
|
shift ;;
|
|
|
|
-t|--tag-name)
|
|
TAG_NAME="$2"
|
|
shift ;;
|
|
|
|
-h|--help)
|
|
printUsage "merge"
|
|
exit ;;
|
|
|
|
*)
|
|
logError "Unknown option '$arg'\n"
|
|
printUsage "merge"
|
|
exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
init
|
|
|
|
performChecks
|
|
|
|
logInfo "Updating language files..."
|
|
./share/translations/update.sh update
|
|
./share/translations/update.sh pull
|
|
if [ 0 -ne $? ]; then
|
|
exitError "Updating translations failed!"
|
|
fi
|
|
git diff-index --quiet HEAD --
|
|
if [ $? -ne 0 ]; then
|
|
git add ./share/translations/*
|
|
logInfo "Committing changes..."
|
|
if [ "" == "$GPG_GIT_KEY" ]; then
|
|
git commit -m "Update translations"
|
|
else
|
|
git commit -m "Update translations" -S"$GPG_GIT_KEY"
|
|
fi
|
|
fi
|
|
|
|
CHANGELOG=$($GREP -Pzo "(?<=${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n\n)\n?(?:.|\n)+?\n(?=## )" CHANGELOG.md \
|
|
| sed 's/^### //' | tr -d \\0)
|
|
COMMIT_MSG="Release ${RELEASE_NAME}"
|
|
|
|
logInfo "Checking out target branch '${TARGET_BRANCH}'..."
|
|
git checkout "$TARGET_BRANCH"
|
|
|
|
logInfo "Merging '${SOURCE_BRANCH}' into '${TARGET_BRANCH}'..."
|
|
|
|
git merge "$SOURCE_BRANCH" --no-ff -m "$COMMIT_MSG" -m "${CHANGELOG}" "$SOURCE_BRANCH" -S"$GPG_GIT_KEY"
|
|
|
|
logInfo "Creating tag '${TAG_NAME}'..."
|
|
if [ "" == "$GPG_GIT_KEY" ]; then
|
|
git tag -a "$TAG_NAME" -m "$COMMIT_MSG" -m "${CHANGELOG}" -s
|
|
else
|
|
git tag -a "$TAG_NAME" -m "$COMMIT_MSG" -m "${CHANGELOG}" -s -u "$GPG_GIT_KEY"
|
|
fi
|
|
|
|
cleanup
|
|
|
|
logInfo "All done!"
|
|
logInfo "Please merge the release branch back into the develop branch now and then push your changes."
|
|
logInfo "Don't forget to also push the tags using \e[1mgit push --tags\e[0m."
|
|
}
|
|
|
|
# -----------------------------------------------------------------------
|
|
# appimage command
|
|
# -----------------------------------------------------------------------
|
|
appimage() {
|
|
local appdir
|
|
local build_appsign=false
|
|
local build_key
|
|
local verbosity="1"
|
|
|
|
while [ $# -ge 1 ]; do
|
|
local arg="$1"
|
|
case "$arg" in
|
|
-v|--version)
|
|
RELEASE_NAME="$2"
|
|
shift ;;
|
|
|
|
-a|--appdir)
|
|
appdir="$2"
|
|
shift ;;
|
|
|
|
-o|--output-dir)
|
|
OUTPUT_DIR="$2"
|
|
shift ;;
|
|
|
|
-d|--docker-image)
|
|
DOCKER_IMAGE="$2"
|
|
shift ;;
|
|
|
|
--container-name)
|
|
DOCKER_CONTAINER_NAME="$2"
|
|
shift ;;
|
|
|
|
--appsign)
|
|
build_appsign=true ;;
|
|
|
|
--verbosity)
|
|
verbosity=$2
|
|
shift ;;
|
|
|
|
-k|--key)
|
|
build_key="$2"
|
|
shift ;;
|
|
|
|
-h|--help)
|
|
printUsage "appimage"
|
|
exit ;;
|
|
|
|
*)
|
|
logError "Unknown option '$arg'\n"
|
|
printUsage "appimage"
|
|
exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
if [ -z "${appdir}" ]; then
|
|
logError "Missing arguments, --appdir is required!\n"
|
|
printUsage "appimage"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -d "${appdir}" ]; then
|
|
logError "AppDir does not exist, please create one with 'make install'!\n"
|
|
exit 1
|
|
elif [ -e "${appdir}/AppRun" ]; then
|
|
logError "AppDir has already been run through linuxdeploy, please create a fresh AppDir with 'make install'.\n"
|
|
exit 1
|
|
fi
|
|
|
|
appdir="$(realpath "$appdir")"
|
|
|
|
local out="${OUTPUT_DIR}"
|
|
if [ "" == "$out" ]; then
|
|
out="."
|
|
fi
|
|
mkdir -p "$out"
|
|
local out_real="$(realpath "$out")"
|
|
cd "$out"
|
|
|
|
local linuxdeploy="linuxdeploy"
|
|
local linuxdeploy_cleanup
|
|
local linuxdeploy_plugin_qt="linuxdeploy-plugin-qt"
|
|
local linuxdeploy_plugin_qt_cleanup
|
|
local appimagetool="appimagetool"
|
|
local appimagetool_cleanup
|
|
|
|
logInfo "Testing for AppImage tools..."
|
|
local docker_test_cmd
|
|
if [ "" != "$DOCKER_IMAGE" ]; then
|
|
docker_test_cmd="docker run --rm ${DOCKER_IMAGE}"
|
|
fi
|
|
|
|
# Test if linuxdeploy and linuxdeploy-plugin-qt are installed
|
|
# on the system or inside the Docker container
|
|
if ! ${docker_test_cmd} which ${linuxdeploy} &> /dev/null; then
|
|
logInfo "Downloading linuxdeploy..."
|
|
linuxdeploy="./linuxdeploy"
|
|
linuxdeploy_cleanup="rm -f ${linuxdeploy}"
|
|
curl -L "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" > "$linuxdeploy"
|
|
chmod +x "$linuxdeploy"
|
|
fi
|
|
if ! ${docker_test_cmd} which ${linuxdeploy_plugin_qt} &> /dev/null; then
|
|
logInfo "Downloading linuxdeploy-plugin-qt..."
|
|
linuxdeploy_plugin_qt="./linuxdeploy-plugin-qt"
|
|
linuxdeploy_plugin_qt_cleanup="rm -f ${linuxdeploy_plugin_qt}"
|
|
curl -L "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" > "$linuxdeploy_plugin_qt"
|
|
chmod +x "$linuxdeploy_plugin_qt"
|
|
fi
|
|
|
|
# appimagetool is always run outside a Docker container, so we can access our GPG keys
|
|
if ! cmdExists ${appimagetool}; then
|
|
logInfo "Downloading appimagetool..."
|
|
appimagetool="./appimagetool"
|
|
appimagetool_cleanup="rm -f ${appimagetool}"
|
|
curl -L "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" > "$appimagetool"
|
|
chmod +x "$appimagetool"
|
|
fi
|
|
|
|
# Create custom AppRun wrapper
|
|
cat << EOF > "${out_real}/KeePassXC-AppRun"
|
|
#!/usr/bin/env bash
|
|
|
|
export PATH="\$(dirname \$0)/usr/bin:\${PATH}"
|
|
export LD_LIBRARY_PATH="\$(dirname \$0)/usr/lib:\${LD_LIBRARY_PATH}"
|
|
|
|
if [ "\${1}" == "cli" ]; then
|
|
shift
|
|
exec keepassxc-cli "\$@"
|
|
elif [ "\${1}" == "proxy" ]; then
|
|
shift
|
|
exec keepassxc-proxy "\$@"
|
|
elif [ -v CHROME_WRAPPER ] || [ -v MOZ_LAUNCHED_CHILD ]; then
|
|
exec keepassxc-proxy "\$@"
|
|
else
|
|
exec keepassxc "\$@"
|
|
fi
|
|
EOF
|
|
chmod +x "${out_real}/KeePassXC-AppRun"
|
|
|
|
# Find .desktop files, icons, and binaries to deploy
|
|
local desktop_file="$(find "$appdir" -name "org.keepassxc.KeePassXC.desktop" | head -n1)"
|
|
local icon="$(find "$appdir" -name 'keepassxc.png' | $GREP -P 'application/256x256/apps/keepassxc.png$' | head -n1)"
|
|
local executables="$(IFS=$'\n' find "$appdir" | $GREP -P '/usr/bin/keepassxc[^/]*$' | xargs -i printf " --executable={}")"
|
|
|
|
logInfo "Collecting libs and patching binaries..."
|
|
if [ "" == "$DOCKER_IMAGE" ]; then
|
|
"$linuxdeploy" --verbosity=${verbosity} --plugin=qt --appdir="$appdir" --desktop-file="$desktop_file" \
|
|
--custom-apprun="${out_real}/KeePassXC-AppRun" --icon-file="$icon" ${executables} \
|
|
--library=$(ldconfig -p | $GREP x86-64 | $GREP -oP '/[^\s]+/libgpg-error\.so\.\d+$' | head -n1)
|
|
else
|
|
desktop_file="${desktop_file//${appdir}/\/keepassxc\/AppDir}"
|
|
icon="${icon//${appdir}/\/keepassxc\/AppDir}"
|
|
executables="${executables//${appdir}/\/keepassxc\/AppDir}"
|
|
|
|
docker run --name "$DOCKER_CONTAINER_NAME" --rm \
|
|
--cap-add SYS_ADMIN --security-opt apparmor:unconfined --device /dev/fuse \
|
|
-v "${appdir}:/keepassxc/AppDir:rw" \
|
|
-v "${out_real}:/keepassxc/out:rw" \
|
|
"$DOCKER_IMAGE" \
|
|
bash -c "cd /keepassxc/out && ${linuxdeploy} --verbosity=${verbosity} --plugin=qt --appdir=/keepassxc/AppDir \
|
|
--custom-apprun="/keepassxc/out/KeePassXC-AppRun" --desktop-file=${desktop_file} --icon-file=${icon} ${executables} \
|
|
--library=\$(ldconfig -p | grep x86-64 | grep -oP '/[^\s]+/libgpg-error\.so\.\d+$' | head -n1)"
|
|
fi
|
|
|
|
logInfo "Creating AppImage..."
|
|
local appsign_flag=""
|
|
local appsign_key_flag=""
|
|
if ${build_appsign}; then
|
|
appsign_flag="--sign"
|
|
appsign_key_flag="--sign-key ${build_key}"
|
|
fi
|
|
local appimage_name="KeePassXC-x86_64.AppImage"
|
|
if [ "" != "$RELEASE_NAME" ]; then
|
|
appimage_name="KeePassXC-${RELEASE_NAME}-x86_64.AppImage"
|
|
fi
|
|
|
|
# Allow appimagetool to insert version information into the AppImage to allow
|
|
# desktop integration tools to display that in app launchers
|
|
export VERSION="${RELEASE_NAME}"
|
|
|
|
# Run appimagetool to package (and possibly sign) AppImage
|
|
# --no-appstream is required, since it may crash on newer systems
|
|
# see: https://github.com/AppImage/AppImageKit/issues/856
|
|
"$appimagetool" --updateinformation "gh-releases-zsync|keepassxreboot|keepassxc|latest|KeePassXC-*-x86_64.AppImage.zsync" \
|
|
${appsign_flag} ${appsign_key_flag} --no-appstream "$appdir" "${out_real}/${appimage_name}"
|
|
|
|
logInfo "Cleaning up temporary files..."
|
|
${linuxdeploy_cleanup}
|
|
${linuxdeploy_plugin_qt_cleanup}
|
|
${appimagetool_cleanup}
|
|
rm -f "${out_real}/KeePassXC-AppRun"
|
|
}
|
|
|
|
# -----------------------------------------------------------------------
|
|
# build command
|
|
# -----------------------------------------------------------------------
|
|
build() {
|
|
local build_source_tarball=true
|
|
local build_snapshot=false
|
|
local build_snapcraft=false
|
|
local build_appimage=false
|
|
local build_generators=""
|
|
local build_appsign=false
|
|
local build_key=""
|
|
|
|
while [ $# -ge 1 ]; do
|
|
local arg="$1"
|
|
case "$arg" in
|
|
-v|--version)
|
|
RELEASE_NAME="$2"
|
|
shift ;;
|
|
|
|
-a|--app-name)
|
|
APP_NAME="$2"
|
|
shift ;;
|
|
|
|
-s|--source-dir)
|
|
SRC_DIR="$2"
|
|
shift ;;
|
|
|
|
-o|--output-dir)
|
|
OUTPUT_DIR="$2"
|
|
shift ;;
|
|
|
|
-t|--tag-name)
|
|
TAG_NAME="$2"
|
|
shift ;;
|
|
|
|
-d|--docker-image)
|
|
DOCKER_IMAGE="$2"
|
|
shift ;;
|
|
|
|
--container-name)
|
|
DOCKER_CONTAINER_NAME="$2"
|
|
shift ;;
|
|
|
|
--appsign)
|
|
build_appsign=true ;;
|
|
|
|
-k|--key)
|
|
build_key="$2"
|
|
shift ;;
|
|
|
|
--snapcraft)
|
|
build_snapcraft=true ;;
|
|
|
|
--appimage)
|
|
build_appimage=true ;;
|
|
|
|
-c|--cmake-options)
|
|
CMAKE_OPTIONS="$2"
|
|
shift ;;
|
|
|
|
--compiler)
|
|
COMPILER="$2"
|
|
shift ;;
|
|
|
|
-m|--make-options)
|
|
MAKE_OPTIONS="$2"
|
|
shift ;;
|
|
|
|
-g|--generators)
|
|
build_generators="$2"
|
|
shift ;;
|
|
|
|
-i|--install-prefix)
|
|
INSTALL_PREFIX="$2"
|
|
shift ;;
|
|
|
|
-p|--plugins)
|
|
BUILD_PLUGINS="$2"
|
|
shift ;;
|
|
|
|
-n|--no-source-tarball)
|
|
build_source_tarball=false ;;
|
|
|
|
--snapshot)
|
|
build_snapshot=true ;;
|
|
|
|
-h|--help)
|
|
printUsage "build"
|
|
exit ;;
|
|
|
|
*)
|
|
logError "Unknown option '$arg'\n"
|
|
printUsage "build"
|
|
exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
init
|
|
|
|
OUTPUT_DIR="$(realpath "$OUTPUT_DIR")"
|
|
# Resolve appsign key to absolute path if under Windows
|
|
if [[ "${build_key}" && "$(uname -o)" == "Msys" ]]; then
|
|
build_key="$(realpath "${build_key}")"
|
|
fi
|
|
|
|
if ${build_snapshot}; then
|
|
TAG_NAME="HEAD"
|
|
local branch=`git rev-parse --abbrev-ref HEAD`
|
|
logInfo "Using current branch ${branch} to build..."
|
|
RELEASE_NAME="${RELEASE_NAME}-snapshot"
|
|
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Snapshot -DOVERRIDE_VERSION=${RELEASE_NAME}"
|
|
else
|
|
checkGrepCompat
|
|
checkWorkingTreeClean
|
|
|
|
if $(echo "$TAG_NAME" | $GREP -qP "\-(alpha|beta)\\d+\$"); then
|
|
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=PreRelease"
|
|
logInfo "Checking out pre-release tag '${TAG_NAME}'..."
|
|
else
|
|
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Release"
|
|
logInfo "Checking out release tag '${TAG_NAME}'..."
|
|
fi
|
|
git checkout "$TAG_NAME"
|
|
fi
|
|
|
|
logInfo "Creating output directory..."
|
|
mkdir -p "$OUTPUT_DIR"
|
|
|
|
if [ $? -ne 0 ]; then
|
|
exitError "Failed to create output directory!"
|
|
fi
|
|
|
|
if ${build_source_tarball}; then
|
|
logInfo "Creating source tarball..."
|
|
local app_name_lower="$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]')"
|
|
local prefix="${app_name_lower}-${RELEASE_NAME}"
|
|
local tarball_name="${prefix}-src.tar"
|
|
|
|
git archive --format=tar "$TAG_NAME" --prefix="${prefix}/" --output="${OUTPUT_DIR}/${tarball_name}"
|
|
|
|
# add .version and .gitrev files to tarball
|
|
mkdir "${prefix}"
|
|
echo -n ${RELEASE_NAME} > "${prefix}/.version"
|
|
echo -n `git rev-parse --short=7 HEAD` > "${prefix}/.gitrev"
|
|
tar --append --file="${OUTPUT_DIR}/${tarball_name}" "${prefix}/.version" "${prefix}/.gitrev"
|
|
rm "${prefix}/.version" "${prefix}/.gitrev"
|
|
rmdir "${prefix}" 2> /dev/null
|
|
|
|
local xz="xz"
|
|
if ! cmdExists xz; then
|
|
logWarn "xz not installed. Falling back to bz2..."
|
|
xz="bzip2"
|
|
fi
|
|
$xz -6 "${OUTPUT_DIR}/${tarball_name}"
|
|
fi
|
|
|
|
if ! ${build_snapshot} && [ -e "${OUTPUT_DIR}/build-release" ]; then
|
|
logInfo "Cleaning existing build directory..."
|
|
rm -r "${OUTPUT_DIR}/build-release" 2> /dev/null
|
|
if [ $? -ne 0 ]; then
|
|
exitError "Failed to clean existing build directory, please do it manually."
|
|
fi
|
|
fi
|
|
|
|
logInfo "Creating build directory..."
|
|
mkdir -p "${OUTPUT_DIR}/build-release"
|
|
cd "${OUTPUT_DIR}/build-release"
|
|
|
|
logInfo "Configuring sources..."
|
|
for p in ${BUILD_PLUGINS}; do
|
|
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_XC_$(echo $p | tr '[:lower:]' '[:upper:]')=On"
|
|
done
|
|
if [ "$(uname -o 2> /dev/null)" == "GNU/Linux" ] && ${build_appimage}; then
|
|
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_DIST_TYPE=AppImage"
|
|
# linuxdeploy requires /usr as install prefix
|
|
INSTALL_PREFIX="/usr"
|
|
fi
|
|
# Do not build tests cases
|
|
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_TESTS=OFF"
|
|
|
|
if [ "$COMPILER" == "g++" ]; then
|
|
export CC=gcc
|
|
elif [ "$COMPILER" == "clang++" ]; then
|
|
export CC=clang
|
|
fi
|
|
export CXX="$COMPILER"
|
|
|
|
if [ "" == "$DOCKER_IMAGE" ]; then
|
|
if [ "$(uname -s)" == "Darwin" ]; then
|
|
# Building on macOS
|
|
export MACOSX_DEPLOYMENT_TARGET
|
|
|
|
logInfo "Configuring build..."
|
|
cmake -DCMAKE_BUILD_TYPE=Release \
|
|
-DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \
|
|
-DCMAKE_PREFIX_PATH="/usr/local/opt/qt/lib/cmake" \
|
|
${CMAKE_OPTIONS} "$SRC_DIR"
|
|
|
|
logInfo "Compiling and packaging sources..."
|
|
make ${MAKE_OPTIONS} package
|
|
|
|
# Appsign the executables if desired
|
|
if ${build_appsign}; then
|
|
logInfo "Signing executable files"
|
|
appsign "-f" "./${APP_NAME}-${RELEASE_NAME}.dmg" "-k" "${build_key}"
|
|
fi
|
|
|
|
mv "./${APP_NAME}-${RELEASE_NAME}.dmg" ../
|
|
elif [ "$(uname -o)" == "Msys" ]; then
|
|
# Building on Windows with Msys2
|
|
logInfo "Configuring build..."
|
|
cmake -DCMAKE_BUILD_TYPE=Release -G"MSYS Makefiles" \
|
|
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" ${CMAKE_OPTIONS} "$SRC_DIR"
|
|
|
|
logInfo "Compiling and packaging sources..."
|
|
mingw32-make ${MAKE_OPTIONS} preinstall
|
|
|
|
# Appsign the executables if desired
|
|
if ${build_appsign} && [ -f "${build_key}" ]; then
|
|
logInfo "Signing executable files"
|
|
appsign "-f" $(find src | $GREP -P '\.exe$|\.dll$') "-k" "${build_key}"
|
|
fi
|
|
|
|
# Call cpack directly instead of calling make package.
|
|
# This is important because we want to build the MSI when making a
|
|
# release.
|
|
cpack -G "${CPACK_GENERATORS};${build_generators}"
|
|
|
|
# Inject the portable config into the zip build and rename
|
|
for filename in ${APP_NAME}-*.zip; do
|
|
logInfo "Creating portable zip file"
|
|
local folder=$(echo ${filename} | sed -r 's/(.*)\.zip/\1/')
|
|
python -c 'import zipfile,sys ; zipfile.ZipFile(sys.argv[1],"a").write(sys.argv[2],sys.argv[3])' \
|
|
${filename} ${SRC_DIR}/share/keepassxc.ini ${folder}/keepassxc.ini
|
|
mv ${filename} ${folder}-portable.zip
|
|
done
|
|
|
|
mv "${APP_NAME}-"*.* ../
|
|
else
|
|
mkdir -p "${OUTPUT_DIR}/KeePassXC.AppDir"
|
|
|
|
# Building on Linux without Docker container
|
|
logInfo "Configuring build..."
|
|
cmake -DCMAKE_BUILD_TYPE=Release ${CMAKE_OPTIONS} \
|
|
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" "$SRC_DIR"
|
|
|
|
logInfo "Compiling sources..."
|
|
make ${MAKE_OPTIONS}
|
|
|
|
logInfo "Installing to bin dir..."
|
|
make DESTDIR="${OUTPUT_DIR}/KeePassXC.AppDir" install/strip
|
|
fi
|
|
else
|
|
if ${build_snapcraft}; then
|
|
logInfo "Building snapcraft docker image..."
|
|
|
|
sudo docker image build -t "$DOCKER_IMAGE" "$(realpath "$SRC_DIR")/ci/snapcraft"
|
|
|
|
logInfo "Launching Docker contain to compile snapcraft..."
|
|
|
|
sudo docker run --name "$DOCKER_CONTAINER_NAME" --rm \
|
|
-v "$(realpath "$SRC_DIR"):/keepassxc" -w "/keepassxc" \
|
|
"$DOCKER_IMAGE" snapcraft
|
|
else
|
|
mkdir -p "${OUTPUT_DIR}/KeePassXC.AppDir"
|
|
|
|
logInfo "Launching Docker container to compile sources..."
|
|
|
|
docker run --name "$DOCKER_CONTAINER_NAME" --rm \
|
|
--cap-add SYS_ADMIN --security-opt apparmor:unconfined --device /dev/fuse \
|
|
-e "CC=${CC}" -e "CXX=${CXX}" \
|
|
-v "$(realpath "$SRC_DIR"):/keepassxc/src:ro" \
|
|
-v "$(realpath "$OUTPUT_DIR"):/keepassxc/out:rw" \
|
|
"$DOCKER_IMAGE" \
|
|
bash -c "cd /keepassxc/out/build-release && \
|
|
cmake -DCMAKE_BUILD_TYPE=Release ${CMAKE_OPTIONS} \
|
|
-DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} /keepassxc/src && \
|
|
make ${MAKE_OPTIONS} && make DESTDIR=/keepassxc/out/KeePassXC.AppDir install/strip"
|
|
fi
|
|
|
|
if [ 0 -ne $? ]; then
|
|
exitError "Docker build failed!"
|
|
fi
|
|
|
|
logInfo "Build finished, Docker container terminated."
|
|
fi
|
|
|
|
if [ "$(uname -o 2> /dev/null)" == "GNU/Linux" ] && ${build_appimage}; then
|
|
local appsign_flag=""
|
|
local appsign_key_flag=""
|
|
local docker_image_flag=""
|
|
local docker_container_name_flag=""
|
|
if ${build_appsign}; then
|
|
appsign_flag="--appsign"
|
|
appsign_key_flag="-k ${build_key}"
|
|
fi
|
|
if [ "" != "${DOCKER_IMAGE}" ]; then
|
|
docker_image_flag="-d ${DOCKER_IMAGE}"
|
|
docker_container_name_flag="--container-name ${DOCKER_CONTAINER_NAME}"
|
|
fi
|
|
appimage "-a" "${OUTPUT_DIR}/KeePassXC.AppDir" "-o" "${OUTPUT_DIR}" \
|
|
${appsign_flag} ${appsign_key_flag} ${docker_image_flag} ${docker_container_name_flag}
|
|
fi
|
|
|
|
cleanup
|
|
|
|
logInfo "All done!"
|
|
}
|
|
|
|
# -----------------------------------------------------------------------
|
|
# gpgsign command
|
|
# -----------------------------------------------------------------------
|
|
gpgsign() {
|
|
local sign_files=()
|
|
|
|
while [ $# -ge 1 ]; do
|
|
local arg="$1"
|
|
case "$arg" in
|
|
-f|--files)
|
|
while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
|
|
sign_files+=("$2")
|
|
shift
|
|
done ;;
|
|
|
|
-k|--key|-g|--gpg-key)
|
|
GPG_KEY="$2"
|
|
shift ;;
|
|
|
|
-h|--help)
|
|
printUsage "gpgsign"
|
|
exit ;;
|
|
|
|
*)
|
|
logError "Unknown option '$arg'\n"
|
|
printUsage "gpgsign"
|
|
exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
if [ -z "${sign_files}" ]; then
|
|
logError "Missing arguments, --files is required!\n"
|
|
printUsage "gpgsign"
|
|
exit 1
|
|
fi
|
|
|
|
for f in "${sign_files[@]}"; do
|
|
if [ ! -f "$f" ]; then
|
|
exitError "File '${f}' does not exist or is not a file!"
|
|
fi
|
|
|
|
logInfo "Signing file '${f}' using release key..."
|
|
gpg --output "${f}.sig" --armor --local-user "$GPG_KEY" --detach-sig "$f"
|
|
|
|
if [ 0 -ne $? ]; then
|
|
exitError "Signing failed!"
|
|
fi
|
|
|
|
logInfo "Creating digest for file '${f}'..."
|
|
local rp="$(realpath "$f")"
|
|
local bname="$(basename "$f")"
|
|
(cd "$(dirname "$rp")"; sha256sum "$bname" > "${bname}.DIGEST")
|
|
done
|
|
|
|
logInfo "All done!"
|
|
}
|
|
|
|
# -----------------------------------------------------------------------
|
|
# appsign command
|
|
# -----------------------------------------------------------------------
|
|
appsign() {
|
|
local sign_files=()
|
|
local key
|
|
local ac_username
|
|
local ac_keychain="AC_PASSWORD"
|
|
|
|
while [ $# -ge 1 ]; do
|
|
local arg="$1"
|
|
case "$arg" in
|
|
-f|--files)
|
|
while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
|
|
sign_files+=("$2")
|
|
shift
|
|
done ;;
|
|
|
|
-k|--key|-i|--identity)
|
|
key="$2"
|
|
shift ;;
|
|
|
|
-u|--username)
|
|
ac_username="$2"
|
|
shift ;;
|
|
|
|
-c|--keychain)
|
|
ac_keychain="$2"
|
|
shift ;;
|
|
|
|
-h|--help)
|
|
printUsage "appsign"
|
|
exit ;;
|
|
|
|
*)
|
|
logError "Unknown option '$arg'\n"
|
|
printUsage "appsign"
|
|
exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
if [ -z "${key}" ]; then
|
|
logError "Missing arguments, --key is required!\n"
|
|
printUsage "appsign"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "${sign_files}" ]; then
|
|
logError "Missing arguments, --files is required!\n"
|
|
printUsage "appsign"
|
|
exit 1
|
|
fi
|
|
|
|
for f in "${sign_files[@]}"; do
|
|
if [ ! -f "${f}" ]; then
|
|
exitError "File '${f}' does not exist or is not a file!"
|
|
fi
|
|
done
|
|
|
|
if [ "$(uname -s)" == "Darwin" ]; then
|
|
if [ "$ac_username" == "" ]; then
|
|
exitError "Missing arguments, --username is required!"
|
|
fi
|
|
|
|
checkXcodeSetup
|
|
checkGrepCompat
|
|
|
|
local orig_dir="$(pwd)"
|
|
local real_src_dir="$(realpath "${SRC_DIR}")"
|
|
for f in "${sign_files[@]}"; do
|
|
if [[ ${f: -4} == '.dmg' ]]; then
|
|
logInfo "Unpacking disk image '${f}'..."
|
|
local tmp_dir="/tmp/KeePassXC_${RANDOM}"
|
|
mkdir -p ${tmp_dir}/mnt
|
|
hdiutil attach -quiet -noautoopen -mountpoint ${tmp_dir}/mnt "${f}"
|
|
cd ${tmp_dir}
|
|
cp -a ./mnt ./app
|
|
hdiutil detach -quiet ${tmp_dir}/mnt
|
|
|
|
if [ ! -d ./app/KeePassXC.app ]; then
|
|
cd "${orig_dir}"
|
|
exitError "Unpacking failed!"
|
|
fi
|
|
|
|
logInfo "Signing app bundle..."
|
|
xcrun codesign --sign "${key}" --verbose --deep --options runtime ./app/KeePassXC.app
|
|
|
|
# Sign main binary and libraries independently so we can keep using the convenient --deep
|
|
# option while avoiding adding entitlements recursively
|
|
logInfo "Signing main binary..."
|
|
xcrun codesign --sign "${key}" --verbose --force --options runtime --entitlements \
|
|
"${real_src_dir}/share/macosx/keepassxc.entitlements" ./app/KeePassXC.app/Contents/MacOS/KeePassXC
|
|
|
|
if [ 0 -ne $? ]; then
|
|
cd "${orig_dir}"
|
|
exitError "Signing failed!"
|
|
fi
|
|
|
|
logInfo "Repacking disk image..."
|
|
hdiutil create \
|
|
-volname "KeePassXC" \
|
|
-size $((1000 * ($(du -sk ./app | cut -f1) + 5000))) \
|
|
-srcfolder ./app \
|
|
-fs HFS+ \
|
|
-fsargs "-c c=64,a=16,e=16" \
|
|
-format UDBZ \
|
|
"${tmp_dir}/$(basename "${f}")"
|
|
|
|
cd "${orig_dir}"
|
|
cp -f "${tmp_dir}/$(basename "${f}")" "${f}"
|
|
rm -Rf ${tmp_dir}
|
|
|
|
logInfo "Submitting disk image for notarization..."
|
|
local status="$(xcrun altool --notarize-app \
|
|
--primary-bundle-id "org.keepassxc.keepassxc" \
|
|
--username "${ac_username}" \
|
|
--password "@keychain:${ac_keychain}" \
|
|
--file "${f}")"
|
|
|
|
if [ 0 -ne $? ]; then
|
|
logError "Submission failed!"
|
|
exitError "Error message:\n${status}"
|
|
fi
|
|
|
|
local ticket="$(echo "${status}" | $GREP -oP "[a-f0-9-]+$")"
|
|
logInfo "Submission successful. Ticket ID: ${ticket}."
|
|
|
|
logInfo "Waiting for notarization to finish (this may take a while)..."
|
|
while true; do
|
|
echo -n "."
|
|
|
|
status="$(xcrun altool --notarization-info "${ticket}" \
|
|
--username "${ac_username}" \
|
|
--password "@keychain:${ac_keychain}")"
|
|
|
|
if echo "$status" | $GREP -q "Status Code: 0"; then
|
|
logInfo "\nNotarization successful."
|
|
break
|
|
elif echo "$status" | $GREP -q "Status Code"; then
|
|
logError "\nNotarization failed!"
|
|
exitError "Error message:\n${status}"
|
|
fi
|
|
|
|
sleep 5
|
|
done
|
|
|
|
logInfo "Stapling ticket to disk image..."
|
|
xcrun stapler staple "${f}"
|
|
|
|
if [ 0 -ne $? ]; then
|
|
exitError "Stapling failed!"
|
|
fi
|
|
|
|
logInfo "Disk image successfully signed and notarized."
|
|
else
|
|
logWarn "Skipping non-DMG file '${f}'..."
|
|
fi
|
|
done
|
|
|
|
elif [ "$(uname -o)" == "Msys" ]; then
|
|
if [[ ! -f "${key}" ]]; then
|
|
exitError "Key file was not found!"
|
|
fi
|
|
|
|
read -s -p "Key password: " password
|
|
echo
|
|
|
|
for f in "${sign_files[@]}"; do
|
|
ext=${f: -4}
|
|
if [[ $ext == ".msi" || $ext == ".exe" || $ext == ".dll" ]]; then
|
|
# Make sure we can find the signtool
|
|
checkSigntoolCommandExists
|
|
|
|
# osslsigncode does not succeed at signing MSI files at this time...
|
|
logInfo "Signing file '${f}' using Microsoft signtool..."
|
|
signtool sign -f "${key}" -p "${password}" -d "KeePassXC" \
|
|
-t "http://timestamp.comodoca.com/authenticode" "${f}"
|
|
|
|
if [ 0 -ne $? ]; then
|
|
exitError "Signing failed!"
|
|
fi
|
|
else
|
|
logInfo "Skipping non-executable file '${f}'..."
|
|
fi
|
|
done
|
|
|
|
else
|
|
exitError "Unsupported platform for code signing!\n"
|
|
fi
|
|
|
|
logInfo "All done!"
|
|
}
|
|
|
|
# -----------------------------------------------------------------------
|
|
# parse global command line
|
|
# -----------------------------------------------------------------------
|
|
MODE="$1"
|
|
shift
|
|
if [ "" == "$MODE" ]; then
|
|
logError "Missing arguments!\n"
|
|
printUsage
|
|
exit 1
|
|
elif [ "help" == "$MODE" ]; then
|
|
printUsage "$1"
|
|
exit
|
|
elif [ "check" == "$MODE" ] || [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] \
|
|
|| [ "gpgsign" == "$MODE" ] || [ "appsign" == "$MODE" ] || [ "appimage" == "$MODE" ]; then
|
|
${MODE} "$@"
|
|
else
|
|
printUsage "$MODE"
|
|
fi
|