1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426 |
- #!/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="-j$(getconf _NPROCESSORS_ONLN)"
- BUILD_PLUGINS="all"
- INSTALL_PREFIX="/usr/local"
- ORIG_BRANCH=""
- ORIG_CWD="$(pwd)"
- MACOSX_DEPLOYMENT_TARGET=10.13
- GREP="grep"
- TIMESTAMP_SERVER="http://timestamp.sectigo.com"
- # -----------------------------------------------------------------------
- # helper functions
- # -----------------------------------------------------------------------
- printUsage() {
- local cmd
- if [ "" == "$1" ] || [ "help" == "$1" ]; then
- cmd="COMMAND"
- elif [ "check" == "$1" ] || [ "merge" == "$1" ] || [ "build" == "$1" ] \
- || [ "gpgsign" == "$1" ] || [ "appsign" == "$1" ] || [ "notarize" == "$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
- notarize Submit macOS application DMG for notarization
- 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
- --timestamp Explicitly set the timestamp server to use for appsign (default: '${TIMESTAMP_SERVER}')
- -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)
- --timestamp Explicitly set the timestamp server to use for appsign (default: '${TIMESTAMP_SERVER}')
- -u, --username Apple username for notarization (required on macOS)
- -h, --help Show this help
- EOF
- elif [ "notarize" == "$cmd" ]; then
- cat << EOF
- Submit macOS application DMG for notarization
- Options:
- -f, --files Files to notarize (required)
- -u, --username Apple username for notarization (required)
- -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" > /dev/null 2>&1
- 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 ;;
- --timestamp)
- TIMESTAMP_SERVER="$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 -A ./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" > /dev/null 2>&1
- 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
- exitError "AppDir does not exist, please create one with 'make install'!"
- elif [ -e "${appdir}/AppRun" ]; then
- exitError "AppDir has already been run through linuxdeploy, please create a fresh AppDir with 'make install'."
- 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}"
- if ! curl -Lf "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" > "$linuxdeploy"; then
- exitError "linuxdeploy download failed."
- fi
- 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}"
- if ! curl -Lf "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" > "$linuxdeploy_plugin_qt"; then
- exitError "linuxdeploy-plugin-qt download failed."
- fi
- 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}"
- if ! curl -Lf "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" > "$appimagetool"; then
- exitError "appimagetool download failed."
- fi
- 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
- if [ $? -ne 0 ]; then
- exitError "AppDir deployment failed."
- 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"
- echo "X-AppImage-Version=${RELEASE_NAME}" >> "$desktop_file"
- fi
- # 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
- if ! "$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}"; then
- exitError "AppImage creation failed."
- fi
- 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 ;;
- --timestamp)
- TIMESTAMP_SERVER="$2"
- shift ;;
- -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" > /dev/null 2>&1
- 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 -rf "${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="$(uname -m)" -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \
- -DCMAKE_PREFIX_PATH="/opt/homebrew/opt/qt/lib/cmake;/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" "../${APP_NAME}-${RELEASE_NAME}-$(uname -m).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
- touch .portable
- 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} .portable ${folder}/.portable
- mv ${filename} ${folder}-portable.zip
- done
- rm .portable
- 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
- 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 ;;
- -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 [ ! -e "${f}" ]; then
- exitError "File '${f}' does not exist!"
- fi
- done
- if [ "$(uname -s)" == "Darwin" ]; then
- 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
- if ! hdiutil attach -quiet -noautoopen -mountpoint ${tmp_dir}/mnt "${f}"; then
- exitError "DMG mount failed!"
- fi
- cd ${tmp_dir}
- cp -a ./mnt ./app
- hdiutil detach -quiet ${tmp_dir}/mnt
- local app_dir_tmp="./app/KeePassXC.app"
- if [ ! -d "$app_dir_tmp" ]; then
- cd "${orig_dir}"
- exitError "Unpacking failed!"
- fi
- elif [[ ${f: -4} == '.app' ]]; then
- local app_dir_tmp="$f"
- else
- logWarn "Skipping non-app file '${f}'..."
- continue
- fi
- logInfo "Signing libraries and frameworks..."
- if ! find "$app_dir_tmp" \( -name '*.dylib' -o -name '*.so' -o -name '*.framework' \) -print0 | xargs -0 \
- xcrun codesign --sign "${key}" --verbose --force --options runtime; then
- cd "${orig_dir}"
- exitError "Signing failed!"
- fi
- logInfo "Signing executables..."
- if ! find "${app_dir_tmp}/Contents/MacOS" \( -type f -not -name KeePassXC \) -print0 | xargs -0 \
- xcrun codesign --sign "${key}" --verbose --force --options runtime; then
- cd "${orig_dir}"
- exitError "Signing failed!"
- fi
- # Sign main executable with additional entitlements
- if ! xcrun codesign --sign "${key}" --verbose --force --options runtime --entitlements \
- "${real_src_dir}/share/macosx/keepassxc.entitlements" "${app_dir_tmp}/Contents/MacOS/KeePassXC"; then
- cd "${orig_dir}"
- exitError "Signing failed!"
- fi
- if [[ ${f: -4} == '.dmg' ]]; then
- 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}
- fi
- logInfo "File '${f}' successfully signed."
- 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" -td sha256 \
- -fd sha256 -tr "${TIMESTAMP_SERVER}" "${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!"
- }
- # -----------------------------------------------------------------------
- # notarize command
- # -----------------------------------------------------------------------
- notarize() {
- local notarize_files=()
- 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
- notarize_files+=("$2")
- shift
- done ;;
- -u|--username)
- ac_username="$2"
- shift ;;
- -c|--keychain)
- ac_keychain="$2"
- shift ;;
- -h|--help)
- printUsage "notarize"
- exit ;;
- *)
- logError "Unknown option '$arg'\n"
- printUsage "notarize"
- exit 1 ;;
- esac
- shift
- done
- if [ "$(uname -s)" != "Darwin" ]; then
- exitError "Notarization is only supported on macOS!"
- fi
- if [ -z "${notarize_files}" ]; then
- logError "Missing arguments, --files is required!\n"
- printUsage "notarize"
- exit 1
- fi
- if [ "$ac_username" == "" ]; then
- logError "Missing arguments, --username is required!"
- printUsage "notarize"
- exit 1
- fi
- for f in "${notarize_files[@]}"; do
- if [[ ${f: -4} != '.dmg' ]]; then
- logWarn "Skipping non-DMG file '${f}'..."
- continue
- fi
- logInfo "Submitting disk image '${f}' for notarization..."
- local status
- status="$(xcrun altool --notarize-app \
- --primary-bundle-id "org.keepassxc.keepassxc" \
- --username "${ac_username}" \
- --password "@keychain:${ac_keychain}" \
- --file "${f}" 2> /dev/null)"
- 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}" 2> /dev/null)"
- 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 notarized."
- 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" ]|| [ "notarize" == "$MODE" ] \
- || [ "appimage" == "$MODE" ]; then
- ${MODE} "$@"
- else
- printUsage "$MODE"
- fi
|