1#!/usr/bin/env bash 2# 3# KeePassXC Release Preparation Helper 4# Copyright (C) 2017 KeePassXC team <https://keepassxc.org/> 5# 6# This program is free software: you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation, either version 2 or (at your option) 9# version 3 of the License. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program. If not, see <http://www.gnu.org/licenses/>. 18 19printf "\e[1m\e[32mKeePassXC\e[0m Release Preparation Helper\n" 20printf "Copyright (C) 2017 KeePassXC Team <https://keepassxc.org/>\n\n" 21 22 23# ----------------------------------------------------------------------- 24# global default values 25# ----------------------------------------------------------------------- 26RELEASE_NAME="" 27APP_NAME="KeePassXC" 28SRC_DIR="." 29GPG_KEY="CFB4C2166397D0D2" 30GPG_GIT_KEY="" 31OUTPUT_DIR="release" 32SOURCE_BRANCH="" 33TARGET_BRANCH="master" 34TAG_NAME="" 35DOCKER_IMAGE="" 36DOCKER_CONTAINER_NAME="keepassxc-build-container" 37CMAKE_OPTIONS="" 38CPACK_GENERATORS="WIX;ZIP" 39COMPILER="g++" 40MAKE_OPTIONS="-j8" 41BUILD_PLUGINS="all" 42INSTALL_PREFIX="/usr/local" 43ORIG_BRANCH="" 44ORIG_CWD="$(pwd)" 45MACOSX_DEPLOYMENT_TARGET=10.12 46GREP="grep" 47TIMESTAMP_SERVER="http://timestamp.sectigo.com" 48 49# ----------------------------------------------------------------------- 50# helper functions 51# ----------------------------------------------------------------------- 52printUsage() { 53 local cmd 54 if [ "" == "$1" ] || [ "help" == "$1" ]; then 55 cmd="COMMAND" 56 elif [ "check" == "$1" ] || [ "merge" == "$1" ] || [ "build" == "$1" ] \ 57 || [ "gpgsign" == "$1" ] || [ "appsign" == "$1" ] || [ "appimage" == "$1" ]; then 58 cmd="$1" 59 else 60 logError "Unknown command: '$1'\n" 61 cmd="COMMAND" 62 fi 63 64 printf "\e[1mUsage:\e[0m $(basename $0) $cmd [options]\n" 65 66 if [ "COMMAND" == "$cmd" ]; then 67 cat << EOF 68 69Commands: 70 check Perform a dry-run check, nothing is changed 71 merge Merge release branch into main branch and create release tags 72 build Build and package binary release from sources 73 gpgsign Sign previously compiled release packages with GPG 74 appsign Sign binaries with code signing certificates on Windows and macOS 75 help Show help for the given command 76EOF 77 elif [ "merge" == "$cmd" ]; then 78 cat << EOF 79 80Merge release branch into main branch and create release tags 81 82Options: 83 -v, --version Release version number or name (required) 84 -a, --app-name Application name (default: '${APP_NAME}') 85 -s, --source-dir Source directory (default: '${SRC_DIR}') 86 -k, --key GPG key used to sign the merge commit and release tag, 87 leave empty to let Git choose your default key 88 (default: '${GPG_GIT_KEY}') 89 -r, --release-branch Source release branch to merge from (default: 'release/VERSION') 90 --target-branch Target branch to merge to (default: '${TARGET_BRANCH}') 91 -t, --tag-name Override release tag name (defaults to version number) 92 -h, --help Show this help 93EOF 94 elif [ "build" == "$cmd" ]; then 95 cat << EOF 96 97Build and package binary release from sources 98 99Options: 100 -v, --version Release version number or name (required) 101 -a, --app-name Application name (default: '${APP_NAME}') 102 -s, --source-dir Source directory (default: '${SRC_DIR}') 103 -o, --output-dir Output directory where to build the release 104 (default: '${OUTPUT_DIR}') 105 -t, --tag-name Release tag to check out (defaults to version number) 106 -b, --build Build sources after exporting release 107 -d, --docker-image Use the specified Docker image to compile the application. 108 The image must have all required build dependencies installed. 109 This option has no effect if --build is not set. 110 --container-name Docker container name (default: '${DOCKER_CONTAINER_NAME}') 111 The container must not exist already 112 --snapcraft Create and use docker image to build snapcraft distribution. 113 This option has no effect if --docker-image is not set. 114 --appimage Build a Linux AppImage after compilation. 115 If this option is set, --install-prefix has no effect 116 --appsign Perform platform specific App Signing before packaging 117 --timestamp Explicitly set the timestamp server to use for appsign (default: '${TIMESTAMP_SERVER}') 118 -k, --key Specify the App Signing Key/Identity 119 -c, --cmake-options Additional CMake options for compiling the sources 120 --compiler Compiler to use (default: '${COMPILER}') 121 -m, --make-options Make options for compiling sources (default: '${MAKE_OPTIONS}') 122 -g, --generators Additional CPack generators (default: '${CPACK_GENERATORS}') 123 -i, --install-prefix Install prefix (default: '${INSTALL_PREFIX}') 124 -p, --plugins Space-separated list of plugins to build 125 (default: ${BUILD_PLUGINS}) 126 --snapshot Don't checkout the release tag 127 -n, --no-source-tarball Don't build source tarball 128 -h, --help Show this help 129EOF 130 elif [ "gpgsign" == "$cmd" ]; then 131 cat << EOF 132 133Sign previously compiled release packages with GPG 134 135Options: 136 -f, --files Files to sign (required) 137 -k, --key GPG key used to sign the files (default: '${GPG_KEY}') 138 -h, --help Show this help 139EOF 140 elif [ "appsign" == "$cmd" ]; then 141 cat << EOF 142 143Sign binaries with code signing certificates on Windows and macOS 144 145Options: 146 -f, --files Files to sign (required) 147 -k, --key, -i, --identity 148 Signing Key or Apple Developer ID (required) 149 --timestamp Explicitly set the timestamp server to use for appsign (default: '${TIMESTAMP_SERVER}') 150 -u, --username Apple username for notarization (required on macOS) 151 -c, --keychain Apple keychain entry name storing the notarization 152 app password (default: 'AC_PASSWORD') 153 -h, --help Show this help 154EOF 155 elif [ "appimage" == "$cmd" ]; then 156 cat << EOF 157 158Generate Linux AppImage from 'make install' AppDir 159 160Options: 161 -a, --appdir Input AppDir (required) 162 -v, --version KeePassXC version 163 -o, --output-dir Output directory where to build the AppImage 164 (default: '${OUTPUT_DIR}') 165 -d, --docker-image Use the specified Docker image to build the AppImage. 166 The image must have all required build dependencies installed. 167 --container-name Docker container name (default: '${DOCKER_CONTAINER_NAME}') 168 The container must not exist already 169 --appsign Embed a PGP signature into the AppImage 170 -k, --key The PGP Signing Key 171 --verbosity linuxdeploy verbosity (default: 3) 172 -h, --help Show this help 173EOF 174 fi 175} 176 177logInfo() { 178 printf "\e[1m[ \e[34mINFO\e[39m ]\e[0m $1\n" 179} 180 181logWarn() { 182 printf "\e[1m[ \e[33mWARNING\e[39m ]\e[0m $1\n" 183} 184 185logError() { 186 printf "\e[1m[ \e[31mERROR\e[39m ]\e[0m $1\n" >&2 187} 188 189init() { 190 if [ "" == "$RELEASE_NAME" ]; then 191 logError "Missing arguments, --version is required!\n" 192 printUsage "check" 193 exit 1 194 fi 195 196 if [ "" == "$TAG_NAME" ]; then 197 TAG_NAME="$RELEASE_NAME" 198 fi 199 200 if [ "" == "$SOURCE_BRANCH" ]; then 201 SOURCE_BRANCH="release/${RELEASE_NAME}" 202 fi 203 204 ORIG_CWD="$(pwd)" 205 SRC_DIR="$(realpath "$SRC_DIR")" 206 cd "$SRC_DIR" > /dev/null 2>&1 207 ORIG_BRANCH="$(git rev-parse --abbrev-ref HEAD 2> /dev/null)" 208 cd "$ORIG_CWD" 209} 210 211cleanup() { 212 logInfo "Checking out original branch..." 213 if [ "" != "$ORIG_BRANCH" ]; then 214 git checkout "$ORIG_BRANCH" > /dev/null 2>&1 215 fi 216 logInfo "Leaving source directory..." 217 cd "$ORIG_CWD" 218} 219 220exitError() { 221 logError "$1" 222 cleanup 223 exit 1 224} 225 226exitTrap() { 227 exitError "Existing upon user request..." 228} 229 230cmdExists() { 231 command -v "$1" &> /dev/null 232} 233 234checkGrepCompat() { 235 if ! grep -qPzo test <(echo test) 2> /dev/null; then 236 if [ -e /usr/local/opt/grep/libexec/gnubin/grep ]; then 237 GREP="/usr/local/opt/grep/libexec/gnubin/grep" 238 else 239 exitError "Incompatible grep implementation! If on macOS, please run 'brew install grep'." 240 fi 241 fi 242} 243 244checkSourceDirExists() { 245 if [ ! -d "$SRC_DIR" ]; then 246 exitError "Source directory '${SRC_DIR}' does not exist!" 247 fi 248} 249 250checkOutputDirDoesNotExist() { 251 if [ -e "$OUTPUT_DIR" ]; then 252 exitError "Output directory '$OUTPUT_DIR' already exists. Please choose a different location!" 253 fi 254} 255 256checkGitRepository() { 257 if [ ! -d .git ] || [ ! -f CHANGELOG.md ]; then 258 exitError "Source directory is not a valid Git repository!" 259 fi 260} 261 262checkReleaseDoesNotExist() { 263 git tag | $GREP -q "^$TAG_NAME$" 264 if [ $? -eq 0 ]; then 265 exitError "Release '$RELEASE_NAME' (tag: '$TAG_NAME') already exists!" 266 fi 267} 268 269checkWorkingTreeClean() { 270 git diff-index --quiet HEAD -- 271 if [ $? -ne 0 ]; then 272 exitError "Current working tree is not clean! Please commit or unstage any changes." 273 fi 274} 275 276checkSourceBranchExists() { 277 git rev-parse "$SOURCE_BRANCH" > /dev/null 2>&1 278 if [ $? -ne 0 ]; then 279 exitError "Source branch '$SOURCE_BRANCH' does not exist!" 280 fi 281} 282 283checkTargetBranchExists() { 284 git rev-parse "$TARGET_BRANCH" > /dev/null 2>&1 285 if [ $? -ne 0 ]; then 286 exitError "Target branch '$TARGET_BRANCH' does not exist!" 287 fi 288} 289 290checkVersionInCMake() { 291 local app_name_upper="$(echo "$APP_NAME" | tr '[:lower:]' '[:upper:]')" 292 local major_num="$(echo ${RELEASE_NAME} | cut -f1 -d.)" 293 local minor_num="$(echo ${RELEASE_NAME} | cut -f2 -d.)" 294 local patch_num="$(echo ${RELEASE_NAME} | cut -f3 -d. | cut -f1 -d-)" 295 296 $GREP -q "${app_name_upper}_VERSION_MAJOR \"${major_num}\"" CMakeLists.txt 297 if [ $? -ne 0 ]; then 298 exitError "${app_name_upper}_VERSION_MAJOR not updated to '${major_num}' in CMakeLists.txt!" 299 fi 300 301 $GREP -q "${app_name_upper}_VERSION_MINOR \"${minor_num}\"" CMakeLists.txt 302 if [ $? -ne 0 ]; then 303 exitError "${app_name_upper}_VERSION_MINOR not updated to '${minor_num}' in CMakeLists.txt!" 304 fi 305 306 $GREP -q "${app_name_upper}_VERSION_PATCH \"${patch_num}\"" CMakeLists.txt 307 if [ $? -ne 0 ]; then 308 exitError "${app_name_upper}_VERSION_PATCH not updated to '${patch_num}' in CMakeLists.txt!" 309 fi 310} 311 312checkChangeLog() { 313 if [ ! -f CHANGELOG.md ]; then 314 exitError "No CHANGELOG file found!" 315 fi 316 317 $GREP -qPzo "## ${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n" CHANGELOG.md 318 if [ $? -ne 0 ]; then 319 exitError "'CHANGELOG.md' has not been updated to the '${RELEASE_NAME}' release!" 320 fi 321} 322 323checkAppStreamInfo() { 324 if [ ! -f share/linux/org.keepassxc.KeePassXC.appdata.xml ]; then 325 exitError "No AppStream info file found!" 326 fi 327 328 $GREP -qPzo "<release version=\"${RELEASE_NAME}\" date=\"\d{4}-\d{2}-\d{2}\">" share/linux/org.keepassxc.KeePassXC.appdata.xml 329 if [ $? -ne 0 ]; then 330 exitError "'share/linux/org.keepassxc.KeePassXC.appdata.xml' has not been updated to the '${RELEASE_NAME}' release!" 331 fi 332} 333 334checkSnapcraft() { 335 if [ ! -f snap/snapcraft.yaml ]; then 336 echo "Could not find snap/snapcraft.yaml!" 337 return 338 fi 339 340 $GREP -qPzo "version: ${RELEASE_NAME}" snap/snapcraft.yaml 341 if [ $? -ne 0 ]; then 342 exitError "'snapcraft.yaml' has not been updated to the '${RELEASE_NAME}' release!" 343 fi 344 345 $GREP -qPzo "KEEPASSXC_BUILD_TYPE=Release" snap/snapcraft.yaml 346 if [ $? -ne 0 ]; then 347 exitError "'snapcraft.yaml' is not set for a release build!" 348 fi 349} 350 351checkTransifexCommandExists() { 352 if ! cmdExists tx; then 353 exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'." 354 fi 355} 356 357checkSigntoolCommandExists() { 358 if ! cmdExists signtool; then 359 exitError "signtool command not found on the PATH! Add the Windows SDK binary folder to your PATH." 360 fi 361} 362 363checkXcodeSetup() { 364 if ! cmdExists xcrun; then 365 exitError "xcrun command not found on the PATH! Please check that you have correctly installed Xcode." 366 fi 367 if ! xcrun -f codesign > /dev/null 2>&1; then 368 exitError "codesign command not found. You may need to run 'sudo xcode-select -r' to set up Xcode." 369 fi 370 if ! xcrun -f altool > /dev/null 2>&1; then 371 exitError "altool command not found. You may need to run 'sudo xcode-select -r' to set up Xcode." 372 fi 373 if ! xcrun -f stapler > /dev/null 2>&1; then 374 exitError "stapler command not found. You may need to run 'sudo xcode-select -r' to set up Xcode." 375 fi 376} 377 378checkQt5LUpdateExists() { 379 if cmdExists lupdate && ! $(lupdate -version | $GREP -q "lupdate version 5\."); then 380 if ! cmdExists lupdate-qt5; then 381 exitError "Qt Linguist tool (lupdate-qt5) is not installed! Please install using 'apt install qttools5-dev-tools'" 382 fi 383 fi 384} 385 386performChecks() { 387 logInfo "Performing basic checks..." 388 389 checkGrepCompat 390 391 checkSourceDirExists 392 393 logInfo "Changing to source directory..." 394 cd "${SRC_DIR}" 395 396 logInfo "Validating toolset and repository..." 397 398 checkTransifexCommandExists 399 checkQt5LUpdateExists 400 checkGitRepository 401 checkReleaseDoesNotExist 402 checkWorkingTreeClean 403 checkSourceBranchExists 404 checkTargetBranchExists 405 406 logInfo "Checking out '${SOURCE_BRANCH}'..." 407 git checkout "$SOURCE_BRANCH" 408 409 logInfo "Attempting to find '${RELEASE_NAME}' in various files..." 410 411 checkVersionInCMake 412 checkChangeLog 413 checkAppStreamInfo 414 checkSnapcraft 415 416 logInfo "\e[1m\e[32mAll checks passed!\e[0m" 417} 418 419# re-implement realpath for OS X (thanks mschrag) 420# https://superuser.com/questions/205127/ 421if ! cmdExists realpath; then 422 realpath() { 423 pushd . > /dev/null 424 if [ -d "$1" ]; then 425 cd "$1" 426 dirs -l +0 427 else 428 cd "$(dirname "$1")" 429 cur_dir=$(dirs -l +0) 430 431 if [ "$cur_dir" == "/" ]; then 432 echo "$cur_dir$(basename "$1")" 433 else 434 echo "$cur_dir/$(basename "$1")" 435 fi 436 fi 437 popd > /dev/null 438 } 439fi 440 441 442trap exitTrap SIGINT SIGTERM 443 444# ----------------------------------------------------------------------- 445# check command 446# ----------------------------------------------------------------------- 447check() { 448 while [ $# -ge 1 ]; do 449 local arg="$1" 450 case "$arg" in 451 -v|--version) 452 RELEASE_NAME="$2" 453 shift ;; 454 esac 455 shift 456 done 457 458 init 459 460 performChecks 461 462 cleanup 463 464 logInfo "Congrats! You can successfully merge, build, and sign KeepassXC." 465} 466 467# ----------------------------------------------------------------------- 468# merge command 469# ----------------------------------------------------------------------- 470merge() { 471 while [ $# -ge 1 ]; do 472 local arg="$1" 473 case "$arg" in 474 -v|--version) 475 RELEASE_NAME="$2" 476 shift ;; 477 478 -a|--app-name) 479 APP_NAME="$2" 480 shift ;; 481 482 -s|--source-dir) 483 SRC_DIR="$2" 484 shift ;; 485 486 -k|--key|-g|--gpg-key) 487 GPG_GIT_KEY="$2" 488 shift ;; 489 490 --timestamp) 491 TIMESTAMP_SERVER="$2" 492 shift ;; 493 494 -r|--release-branch) 495 SOURCE_BRANCH="$2" 496 shift ;; 497 498 --target-branch) 499 TARGET_BRANCH="$2" 500 shift ;; 501 502 -t|--tag-name) 503 TAG_NAME="$2" 504 shift ;; 505 506 -h|--help) 507 printUsage "merge" 508 exit ;; 509 510 *) 511 logError "Unknown option '$arg'\n" 512 printUsage "merge" 513 exit 1 ;; 514 esac 515 shift 516 done 517 518 init 519 520 performChecks 521 522 logInfo "Updating language files..." 523 ./share/translations/update.sh update 524 ./share/translations/update.sh pull 525 if [ 0 -ne $? ]; then 526 exitError "Updating translations failed!" 527 fi 528 git diff-index --quiet HEAD -- 529 if [ $? -ne 0 ]; then 530 git add -A ./share/translations/ 531 logInfo "Committing changes..." 532 if [ "" == "$GPG_GIT_KEY" ]; then 533 git commit -m "Update translations" 534 else 535 git commit -m "Update translations" -S"$GPG_GIT_KEY" 536 fi 537 fi 538 539 CHANGELOG=$($GREP -Pzo "(?<=${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n\n)\n?(?:.|\n)+?\n(?=## )" CHANGELOG.md \ 540 | sed 's/^### //' | tr -d \\0) 541 COMMIT_MSG="Release ${RELEASE_NAME}" 542 543 logInfo "Checking out target branch '${TARGET_BRANCH}'..." 544 git checkout "$TARGET_BRANCH" 545 546 logInfo "Merging '${SOURCE_BRANCH}' into '${TARGET_BRANCH}'..." 547 548 git merge "$SOURCE_BRANCH" --no-ff -m "$COMMIT_MSG" -m "${CHANGELOG}" "$SOURCE_BRANCH" -S"$GPG_GIT_KEY" 549 550 logInfo "Creating tag '${TAG_NAME}'..." 551 if [ "" == "$GPG_GIT_KEY" ]; then 552 git tag -a "$TAG_NAME" -m "$COMMIT_MSG" -m "${CHANGELOG}" -s 553 else 554 git tag -a "$TAG_NAME" -m "$COMMIT_MSG" -m "${CHANGELOG}" -s -u "$GPG_GIT_KEY" 555 fi 556 557 cleanup 558 559 logInfo "All done!" 560 logInfo "Please merge the release branch back into the develop branch now and then push your changes." 561 logInfo "Don't forget to also push the tags using \e[1mgit push --tags\e[0m." 562} 563 564# ----------------------------------------------------------------------- 565# appimage command 566# ----------------------------------------------------------------------- 567appimage() { 568 local appdir 569 local build_appsign=false 570 local build_key 571 local verbosity="1" 572 573 while [ $# -ge 1 ]; do 574 local arg="$1" 575 case "$arg" in 576 -v|--version) 577 RELEASE_NAME="$2" 578 shift ;; 579 580 -a|--appdir) 581 appdir="$2" 582 shift ;; 583 584 -o|--output-dir) 585 OUTPUT_DIR="$2" 586 shift ;; 587 588 -d|--docker-image) 589 DOCKER_IMAGE="$2" 590 shift ;; 591 592 --container-name) 593 DOCKER_CONTAINER_NAME="$2" 594 shift ;; 595 596 --appsign) 597 build_appsign=true ;; 598 599 --verbosity) 600 verbosity=$2 601 shift ;; 602 603 -k|--key) 604 build_key="$2" 605 shift ;; 606 607 -h|--help) 608 printUsage "appimage" 609 exit ;; 610 611 *) 612 logError "Unknown option '$arg'\n" 613 printUsage "appimage" 614 exit 1 ;; 615 esac 616 shift 617 done 618 619 if [ -z "${appdir}" ]; then 620 logError "Missing arguments, --appdir is required!\n" 621 printUsage "appimage" 622 exit 1 623 fi 624 625 if [ ! -d "${appdir}" ]; then 626 exitError "AppDir does not exist, please create one with 'make install'!" 627 elif [ -e "${appdir}/AppRun" ]; then 628 exitError "AppDir has already been run through linuxdeploy, please create a fresh AppDir with 'make install'." 629 fi 630 631 appdir="$(realpath "$appdir")" 632 633 local out="${OUTPUT_DIR}" 634 if [ "" == "$out" ]; then 635 out="." 636 fi 637 mkdir -p "$out" 638 local out_real="$(realpath "$out")" 639 cd "$out" 640 641 local linuxdeploy="linuxdeploy" 642 local linuxdeploy_cleanup 643 local linuxdeploy_plugin_qt="linuxdeploy-plugin-qt" 644 local linuxdeploy_plugin_qt_cleanup 645 local appimagetool="appimagetool" 646 local appimagetool_cleanup 647 648 logInfo "Testing for AppImage tools..." 649 local docker_test_cmd 650 if [ "" != "$DOCKER_IMAGE" ]; then 651 docker_test_cmd="docker run --rm ${DOCKER_IMAGE}" 652 fi 653 654 # Test if linuxdeploy and linuxdeploy-plugin-qt are installed 655 # on the system or inside the Docker container 656 if ! ${docker_test_cmd} which ${linuxdeploy} &> /dev/null; then 657 logInfo "Downloading linuxdeploy..." 658 linuxdeploy="./linuxdeploy" 659 linuxdeploy_cleanup="rm -f ${linuxdeploy}" 660 if ! curl -Lf "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" > "$linuxdeploy"; then 661 exitError "linuxdeploy download failed." 662 fi 663 chmod +x "$linuxdeploy" 664 fi 665 if ! ${docker_test_cmd} which ${linuxdeploy_plugin_qt} &> /dev/null; then 666 logInfo "Downloading linuxdeploy-plugin-qt..." 667 linuxdeploy_plugin_qt="./linuxdeploy-plugin-qt" 668 linuxdeploy_plugin_qt_cleanup="rm -f ${linuxdeploy_plugin_qt}" 669 if ! curl -Lf "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" > "$linuxdeploy_plugin_qt"; then 670 exitError "linuxdeploy-plugin-qt download failed." 671 fi 672 chmod +x "$linuxdeploy_plugin_qt" 673 fi 674 675 # appimagetool is always run outside a Docker container, so we can access our GPG keys 676 if ! cmdExists ${appimagetool}; then 677 logInfo "Downloading appimagetool..." 678 appimagetool="./appimagetool" 679 appimagetool_cleanup="rm -f ${appimagetool}" 680 if ! curl -Lf "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" > "$appimagetool"; then 681 exitError "appimagetool download failed." 682 fi 683 chmod +x "$appimagetool" 684 fi 685 686 # Create custom AppRun wrapper 687 cat << EOF > "${out_real}/KeePassXC-AppRun" 688#!/usr/bin/env bash 689 690export PATH="\$(dirname \$0)/usr/bin:\${PATH}" 691export LD_LIBRARY_PATH="\$(dirname \$0)/usr/lib:\${LD_LIBRARY_PATH}" 692 693if [ "\${1}" == "cli" ]; then 694 shift 695 exec keepassxc-cli "\$@" 696elif [ "\${1}" == "proxy" ]; then 697 shift 698 exec keepassxc-proxy "\$@" 699elif [ -v CHROME_WRAPPER ] || [ -v MOZ_LAUNCHED_CHILD ]; then 700 exec keepassxc-proxy "\$@" 701else 702 exec keepassxc "\$@" 703fi 704EOF 705 chmod +x "${out_real}/KeePassXC-AppRun" 706 707 # Find .desktop files, icons, and binaries to deploy 708 local desktop_file="$(find "$appdir" -name "org.keepassxc.KeePassXC.desktop" | head -n1)" 709 local icon="$(find "$appdir" -name 'keepassxc.png' | $GREP -P 'application/256x256/apps/keepassxc.png$' | head -n1)" 710 local executables="$(IFS=$'\n' find "$appdir" | $GREP -P '/usr/bin/keepassxc[^/]*$' | xargs -i printf " --executable={}")" 711 712 logInfo "Collecting libs and patching binaries..." 713 if [ "" == "$DOCKER_IMAGE" ]; then 714 "$linuxdeploy" --verbosity=${verbosity} --plugin=qt --appdir="$appdir" --desktop-file="$desktop_file" \ 715 --custom-apprun="${out_real}/KeePassXC-AppRun" --icon-file="$icon" ${executables} \ 716 --library=$(ldconfig -p | $GREP x86-64 | $GREP -oP '/[^\s]+/libgpg-error\.so\.\d+$' | head -n1) 717 else 718 desktop_file="${desktop_file//${appdir}/\/keepassxc\/AppDir}" 719 icon="${icon//${appdir}/\/keepassxc\/AppDir}" 720 executables="${executables//${appdir}/\/keepassxc\/AppDir}" 721 722 docker run --name "$DOCKER_CONTAINER_NAME" --rm \ 723 --cap-add SYS_ADMIN --security-opt apparmor:unconfined --device /dev/fuse \ 724 -v "${appdir}:/keepassxc/AppDir:rw" \ 725 -v "${out_real}:/keepassxc/out:rw" \ 726 "$DOCKER_IMAGE" \ 727 bash -c "cd /keepassxc/out && ${linuxdeploy} --verbosity=${verbosity} --plugin=qt --appdir=/keepassxc/AppDir \ 728 --custom-apprun="/keepassxc/out/KeePassXC-AppRun" --desktop-file=${desktop_file} --icon-file=${icon} ${executables} \ 729 --library=\$(ldconfig -p | grep x86-64 | grep -oP '/[^\s]+/libgpg-error\.so\.\d+$' | head -n1)" 730 fi 731 732 if [ $? -ne 0 ]; then 733 exitError "AppDir deployment failed." 734 fi 735 736 logInfo "Creating AppImage..." 737 local appsign_flag="" 738 local appsign_key_flag="" 739 if ${build_appsign}; then 740 appsign_flag="--sign" 741 appsign_key_flag="--sign-key ${build_key}" 742 fi 743 local appimage_name="KeePassXC-x86_64.AppImage" 744 if [ "" != "$RELEASE_NAME" ]; then 745 appimage_name="KeePassXC-${RELEASE_NAME}-x86_64.AppImage" 746 echo "X-AppImage-Version=${RELEASE_NAME}" >> "$desktop_file" 747 fi 748 749 # Run appimagetool to package (and possibly sign) AppImage 750 # --no-appstream is required, since it may crash on newer systems 751 # see: https://github.com/AppImage/AppImageKit/issues/856 752 if ! "$appimagetool" --updateinformation "gh-releases-zsync|keepassxreboot|keepassxc|latest|KeePassXC-*-x86_64.AppImage.zsync" \ 753 ${appsign_flag} ${appsign_key_flag} --no-appstream "$appdir" "${out_real}/${appimage_name}"; then 754 exitError "AppImage creation failed." 755 fi 756 757 logInfo "Cleaning up temporary files..." 758 ${linuxdeploy_cleanup} 759 ${linuxdeploy_plugin_qt_cleanup} 760 ${appimagetool_cleanup} 761 rm -f "${out_real}/KeePassXC-AppRun" 762} 763 764# ----------------------------------------------------------------------- 765# build command 766# ----------------------------------------------------------------------- 767build() { 768 local build_source_tarball=true 769 local build_snapshot=false 770 local build_snapcraft=false 771 local build_appimage=false 772 local build_generators="" 773 local build_appsign=false 774 local build_key="" 775 776 while [ $# -ge 1 ]; do 777 local arg="$1" 778 case "$arg" in 779 -v|--version) 780 RELEASE_NAME="$2" 781 shift ;; 782 783 -a|--app-name) 784 APP_NAME="$2" 785 shift ;; 786 787 -s|--source-dir) 788 SRC_DIR="$2" 789 shift ;; 790 791 -o|--output-dir) 792 OUTPUT_DIR="$2" 793 shift ;; 794 795 -t|--tag-name) 796 TAG_NAME="$2" 797 shift ;; 798 799 -d|--docker-image) 800 DOCKER_IMAGE="$2" 801 shift ;; 802 803 --container-name) 804 DOCKER_CONTAINER_NAME="$2" 805 shift ;; 806 807 --appsign) 808 build_appsign=true ;; 809 810 --timestamp) 811 TIMESTAMP_SERVER="$2" 812 shift ;; 813 814 -k|--key) 815 build_key="$2" 816 shift ;; 817 818 --snapcraft) 819 build_snapcraft=true ;; 820 821 --appimage) 822 build_appimage=true ;; 823 824 -c|--cmake-options) 825 CMAKE_OPTIONS="$2" 826 shift ;; 827 828 --compiler) 829 COMPILER="$2" 830 shift ;; 831 832 -m|--make-options) 833 MAKE_OPTIONS="$2" 834 shift ;; 835 836 -g|--generators) 837 build_generators="$2" 838 shift ;; 839 840 -i|--install-prefix) 841 INSTALL_PREFIX="$2" 842 shift ;; 843 844 -p|--plugins) 845 BUILD_PLUGINS="$2" 846 shift ;; 847 848 -n|--no-source-tarball) 849 build_source_tarball=false ;; 850 851 --snapshot) 852 build_snapshot=true ;; 853 854 -h|--help) 855 printUsage "build" 856 exit ;; 857 858 *) 859 logError "Unknown option '$arg'\n" 860 printUsage "build" 861 exit 1 ;; 862 esac 863 shift 864 done 865 866 init 867 868 OUTPUT_DIR="$(realpath "$OUTPUT_DIR")" 869 # Resolve appsign key to absolute path if under Windows 870 if [[ "${build_key}" && "$(uname -o)" == "Msys" ]]; then 871 build_key="$(realpath "${build_key}")" 872 fi 873 874 if ${build_snapshot}; then 875 TAG_NAME="HEAD" 876 local branch=`git rev-parse --abbrev-ref HEAD` 877 logInfo "Using current branch ${branch} to build..." 878 RELEASE_NAME="${RELEASE_NAME}-snapshot" 879 CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Snapshot -DOVERRIDE_VERSION=${RELEASE_NAME}" 880 else 881 checkGrepCompat 882 checkWorkingTreeClean 883 884 if $(echo "$TAG_NAME" | $GREP -qP "\-(alpha|beta)\\d+\$"); then 885 CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=PreRelease" 886 logInfo "Checking out pre-release tag '${TAG_NAME}'..." 887 else 888 CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Release" 889 logInfo "Checking out release tag '${TAG_NAME}'..." 890 fi 891 git checkout "$TAG_NAME" 892 fi 893 894 logInfo "Creating output directory..." 895 mkdir -p "$OUTPUT_DIR" 896 897 if [ $? -ne 0 ]; then 898 exitError "Failed to create output directory!" 899 fi 900 901 if ${build_source_tarball}; then 902 logInfo "Creating source tarball..." 903 local app_name_lower="$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]')" 904 local prefix="${app_name_lower}-${RELEASE_NAME}" 905 local tarball_name="${prefix}-src.tar" 906 907 git archive --format=tar "$TAG_NAME" --prefix="${prefix}/" --output="${OUTPUT_DIR}/${tarball_name}" 908 909 # add .version and .gitrev files to tarball 910 mkdir "${prefix}" 911 echo -n ${RELEASE_NAME} > "${prefix}/.version" 912 echo -n `git rev-parse --short=7 HEAD` > "${prefix}/.gitrev" 913 tar --append --file="${OUTPUT_DIR}/${tarball_name}" "${prefix}/.version" "${prefix}/.gitrev" 914 rm "${prefix}/.version" "${prefix}/.gitrev" 915 rmdir "${prefix}" 2> /dev/null 916 917 local xz="xz" 918 if ! cmdExists xz; then 919 logWarn "xz not installed. Falling back to bz2..." 920 xz="bzip2" 921 fi 922 $xz -6 "${OUTPUT_DIR}/${tarball_name}" 923 fi 924 925 if ! ${build_snapshot} && [ -e "${OUTPUT_DIR}/build-release" ]; then 926 logInfo "Cleaning existing build directory..." 927 rm -rf "${OUTPUT_DIR}/build-release" 2> /dev/null 928 if [ $? -ne 0 ]; then 929 exitError "Failed to clean existing build directory, please do it manually." 930 fi 931 fi 932 933 logInfo "Creating build directory..." 934 mkdir -p "${OUTPUT_DIR}/build-release" 935 cd "${OUTPUT_DIR}/build-release" 936 937 logInfo "Configuring sources..." 938 for p in ${BUILD_PLUGINS}; do 939 CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_XC_$(echo $p | tr '[:lower:]' '[:upper:]')=On" 940 done 941 if [ "$(uname -o 2> /dev/null)" == "GNU/Linux" ] && ${build_appimage}; then 942 CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_DIST_TYPE=AppImage" 943 # linuxdeploy requires /usr as install prefix 944 INSTALL_PREFIX="/usr" 945 fi 946 # Do not build tests cases 947 CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_TESTS=OFF" 948 949 if [ "$COMPILER" == "g++" ]; then 950 export CC=gcc 951 elif [ "$COMPILER" == "clang++" ]; then 952 export CC=clang 953 fi 954 export CXX="$COMPILER" 955 956 if [ "" == "$DOCKER_IMAGE" ]; then 957 if [ "$(uname -s)" == "Darwin" ]; then 958 # Building on macOS 959 export MACOSX_DEPLOYMENT_TARGET 960 961 logInfo "Configuring build..." 962 cmake -DCMAKE_BUILD_TYPE=Release \ 963 -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \ 964 -DCMAKE_PREFIX_PATH="/usr/local/opt/qt/lib/cmake" \ 965 ${CMAKE_OPTIONS} "$SRC_DIR" 966 967 logInfo "Compiling and packaging sources..." 968 make ${MAKE_OPTIONS} package 969 970 # Appsign the executables if desired 971 if ${build_appsign}; then 972 logInfo "Signing executable files" 973 appsign "-f" "./${APP_NAME}-${RELEASE_NAME}.dmg" "-k" "${build_key}" 974 fi 975 976 mv "./${APP_NAME}-${RELEASE_NAME}.dmg" ../ 977 elif [ "$(uname -o)" == "Msys" ]; then 978 # Building on Windows with Msys2 979 logInfo "Configuring build..." 980 cmake -DCMAKE_BUILD_TYPE=Release -G"MSYS Makefiles" \ 981 -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" ${CMAKE_OPTIONS} "$SRC_DIR" 982 983 logInfo "Compiling and packaging sources..." 984 mingw32-make ${MAKE_OPTIONS} preinstall 985 986 # Appsign the executables if desired 987 if ${build_appsign} && [ -f "${build_key}" ]; then 988 logInfo "Signing executable files" 989 appsign "-f" $(find src | $GREP -P '\.exe$|\.dll$') "-k" "${build_key}" 990 fi 991 992 # Call cpack directly instead of calling make package. 993 # This is important because we want to build the MSI when making a 994 # release. 995 cpack -G "${CPACK_GENERATORS};${build_generators}" 996 997 # Inject the portable config into the zip build and rename 998 touch .portable 999 for filename in ${APP_NAME}-*.zip; do 1000 logInfo "Creating portable zip file" 1001 local folder=$(echo ${filename} | sed -r 's/(.*)\.zip/\1/') 1002 python -c 'import zipfile,sys ; zipfile.ZipFile(sys.argv[1],"a").write(sys.argv[2],sys.argv[3])' \ 1003 ${filename} .portable ${folder}/.portable 1004 mv ${filename} ${folder}-portable.zip 1005 done 1006 rm .portable 1007 1008 mv "${APP_NAME}-"*.* ../ 1009 else 1010 mkdir -p "${OUTPUT_DIR}/KeePassXC.AppDir" 1011 1012 # Building on Linux without Docker container 1013 logInfo "Configuring build..." 1014 cmake -DCMAKE_BUILD_TYPE=Release ${CMAKE_OPTIONS} \ 1015 -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" "$SRC_DIR" 1016 1017 logInfo "Compiling sources..." 1018 make ${MAKE_OPTIONS} 1019 1020 logInfo "Installing to bin dir..." 1021 make DESTDIR="${OUTPUT_DIR}/KeePassXC.AppDir" install/strip 1022 fi 1023 else 1024 if ${build_snapcraft}; then 1025 logInfo "Building snapcraft docker image..." 1026 1027 sudo docker image build -t "$DOCKER_IMAGE" "$(realpath "$SRC_DIR")/ci/snapcraft" 1028 1029 logInfo "Launching Docker contain to compile snapcraft..." 1030 1031 sudo docker run --name "$DOCKER_CONTAINER_NAME" --rm \ 1032 -v "$(realpath "$SRC_DIR"):/keepassxc" -w "/keepassxc" \ 1033 "$DOCKER_IMAGE" snapcraft 1034 else 1035 mkdir -p "${OUTPUT_DIR}/KeePassXC.AppDir" 1036 1037 logInfo "Launching Docker container to compile sources..." 1038 1039 docker run --name "$DOCKER_CONTAINER_NAME" --rm \ 1040 --cap-add SYS_ADMIN --security-opt apparmor:unconfined --device /dev/fuse \ 1041 -e "CC=${CC}" -e "CXX=${CXX}" \ 1042 -v "$(realpath "$SRC_DIR"):/keepassxc/src:ro" \ 1043 -v "$(realpath "$OUTPUT_DIR"):/keepassxc/out:rw" \ 1044 "$DOCKER_IMAGE" \ 1045 bash -c "cd /keepassxc/out/build-release && \ 1046 cmake -DCMAKE_BUILD_TYPE=Release ${CMAKE_OPTIONS} \ 1047 -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} /keepassxc/src && \ 1048 make ${MAKE_OPTIONS} && make DESTDIR=/keepassxc/out/KeePassXC.AppDir install/strip" 1049 fi 1050 1051 if [ 0 -ne $? ]; then 1052 exitError "Docker build failed!" 1053 fi 1054 1055 logInfo "Build finished, Docker container terminated." 1056 fi 1057 1058 if [ "$(uname -o 2> /dev/null)" == "GNU/Linux" ] && ${build_appimage}; then 1059 local appsign_flag="" 1060 local appsign_key_flag="" 1061 local docker_image_flag="" 1062 local docker_container_name_flag="" 1063 if ${build_appsign}; then 1064 appsign_flag="--appsign" 1065 appsign_key_flag="-k ${build_key}" 1066 fi 1067 if [ "" != "${DOCKER_IMAGE}" ]; then 1068 docker_image_flag="-d ${DOCKER_IMAGE}" 1069 docker_container_name_flag="--container-name ${DOCKER_CONTAINER_NAME}" 1070 fi 1071 appimage "-a" "${OUTPUT_DIR}/KeePassXC.AppDir" "-o" "${OUTPUT_DIR}" \ 1072 ${appsign_flag} ${appsign_key_flag} ${docker_image_flag} ${docker_container_name_flag} 1073 fi 1074 1075 cleanup 1076 1077 logInfo "All done!" 1078} 1079 1080# ----------------------------------------------------------------------- 1081# gpgsign command 1082# ----------------------------------------------------------------------- 1083gpgsign() { 1084 local sign_files=() 1085 1086 while [ $# -ge 1 ]; do 1087 local arg="$1" 1088 case "$arg" in 1089 -f|--files) 1090 while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do 1091 sign_files+=("$2") 1092 shift 1093 done ;; 1094 1095 -k|--key|-g|--gpg-key) 1096 GPG_KEY="$2" 1097 shift ;; 1098 1099 -h|--help) 1100 printUsage "gpgsign" 1101 exit ;; 1102 1103 *) 1104 logError "Unknown option '$arg'\n" 1105 printUsage "gpgsign" 1106 exit 1 ;; 1107 esac 1108 shift 1109 done 1110 1111 if [ -z "${sign_files}" ]; then 1112 logError "Missing arguments, --files is required!\n" 1113 printUsage "gpgsign" 1114 exit 1 1115 fi 1116 1117 for f in "${sign_files[@]}"; do 1118 if [ ! -f "$f" ]; then 1119 exitError "File '${f}' does not exist or is not a file!" 1120 fi 1121 1122 logInfo "Signing file '${f}' using release key..." 1123 gpg --output "${f}.sig" --armor --local-user "$GPG_KEY" --detach-sig "$f" 1124 1125 if [ 0 -ne $? ]; then 1126 exitError "Signing failed!" 1127 fi 1128 1129 logInfo "Creating digest for file '${f}'..." 1130 local rp="$(realpath "$f")" 1131 local bname="$(basename "$f")" 1132 (cd "$(dirname "$rp")"; sha256sum "$bname" > "${bname}.DIGEST") 1133 done 1134 1135 logInfo "All done!" 1136} 1137 1138# ----------------------------------------------------------------------- 1139# appsign command 1140# ----------------------------------------------------------------------- 1141appsign() { 1142 local sign_files=() 1143 local key 1144 local ac_username 1145 local ac_keychain="AC_PASSWORD" 1146 1147 while [ $# -ge 1 ]; do 1148 local arg="$1" 1149 case "$arg" in 1150 -f|--files) 1151 while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do 1152 sign_files+=("$2") 1153 shift 1154 done ;; 1155 1156 -k|--key|-i|--identity) 1157 key="$2" 1158 shift ;; 1159 1160 -u|--username) 1161 ac_username="$2" 1162 shift ;; 1163 1164 -c|--keychain) 1165 ac_keychain="$2" 1166 shift ;; 1167 1168 -h|--help) 1169 printUsage "appsign" 1170 exit ;; 1171 1172 *) 1173 logError "Unknown option '$arg'\n" 1174 printUsage "appsign" 1175 exit 1 ;; 1176 esac 1177 shift 1178 done 1179 1180 if [ -z "${key}" ]; then 1181 logError "Missing arguments, --key is required!\n" 1182 printUsage "appsign" 1183 exit 1 1184 fi 1185 1186 if [ -z "${sign_files}" ]; then 1187 logError "Missing arguments, --files is required!\n" 1188 printUsage "appsign" 1189 exit 1 1190 fi 1191 1192 for f in "${sign_files[@]}"; do 1193 if [ ! -f "${f}" ]; then 1194 exitError "File '${f}' does not exist or is not a file!" 1195 fi 1196 done 1197 1198 if [ "$(uname -s)" == "Darwin" ]; then 1199 if [ "$ac_username" == "" ]; then 1200 exitError "Missing arguments, --username is required!" 1201 fi 1202 1203 checkXcodeSetup 1204 checkGrepCompat 1205 1206 local orig_dir="$(pwd)" 1207 local real_src_dir="$(realpath "${SRC_DIR}")" 1208 for f in "${sign_files[@]}"; do 1209 if [[ ${f: -4} == '.dmg' ]]; then 1210 logInfo "Unpacking disk image '${f}'..." 1211 local tmp_dir="/tmp/KeePassXC_${RANDOM}" 1212 mkdir -p ${tmp_dir}/mnt 1213 hdiutil attach -quiet -noautoopen -mountpoint ${tmp_dir}/mnt "${f}" 1214 cd ${tmp_dir} 1215 cp -a ./mnt ./app 1216 hdiutil detach -quiet ${tmp_dir}/mnt 1217 1218 if [ ! -d ./app/KeePassXC.app ]; then 1219 cd "${orig_dir}" 1220 exitError "Unpacking failed!" 1221 fi 1222 1223 logInfo "Signing app bundle..." 1224 xcrun codesign --sign "${key}" --verbose --deep --options runtime ./app/KeePassXC.app 1225 1226 # Sign main binary and libraries independently so we can keep using the convenient --deep 1227 # option while avoiding adding entitlements recursively 1228 logInfo "Signing main binary..." 1229 xcrun codesign --sign "${key}" --verbose --force --options runtime --entitlements \ 1230 "${real_src_dir}/share/macosx/keepassxc.entitlements" ./app/KeePassXC.app/Contents/MacOS/KeePassXC 1231 1232 if [ 0 -ne $? ]; then 1233 cd "${orig_dir}" 1234 exitError "Signing failed!" 1235 fi 1236 1237 logInfo "Repacking disk image..." 1238 hdiutil create \ 1239 -volname "KeePassXC" \ 1240 -size $((1000 * ($(du -sk ./app | cut -f1) + 5000))) \ 1241 -srcfolder ./app \ 1242 -fs HFS+ \ 1243 -fsargs "-c c=64,a=16,e=16" \ 1244 -format UDBZ \ 1245 "${tmp_dir}/$(basename "${f}")" 1246 1247 cd "${orig_dir}" 1248 cp -f "${tmp_dir}/$(basename "${f}")" "${f}" 1249 rm -Rf ${tmp_dir} 1250 1251 logInfo "Submitting disk image for notarization..." 1252 local status="$(xcrun altool --notarize-app \ 1253 --primary-bundle-id "org.keepassxc.keepassxc" \ 1254 --username "${ac_username}" \ 1255 --password "@keychain:${ac_keychain}" \ 1256 --file "${f}")" 1257 1258 if [ 0 -ne $? ]; then 1259 logError "Submission failed!" 1260 exitError "Error message:\n${status}" 1261 fi 1262 1263 local ticket="$(echo "${status}" | $GREP -oP "[a-f0-9-]+$")" 1264 logInfo "Submission successful. Ticket ID: ${ticket}." 1265 1266 logInfo "Waiting for notarization to finish (this may take a while)..." 1267 while true; do 1268 echo -n "." 1269 1270 status="$(xcrun altool --notarization-info "${ticket}" \ 1271 --username "${ac_username}" \ 1272 --password "@keychain:${ac_keychain}")" 1273 1274 if echo "$status" | $GREP -q "Status Code: 0"; then 1275 logInfo "\nNotarization successful." 1276 break 1277 elif echo "$status" | $GREP -q "Status Code"; then 1278 logError "\nNotarization failed!" 1279 exitError "Error message:\n${status}" 1280 fi 1281 1282 sleep 5 1283 done 1284 1285 logInfo "Stapling ticket to disk image..." 1286 xcrun stapler staple "${f}" 1287 1288 if [ 0 -ne $? ]; then 1289 exitError "Stapling failed!" 1290 fi 1291 1292 logInfo "Disk image successfully signed and notarized." 1293 else 1294 logWarn "Skipping non-DMG file '${f}'..." 1295 fi 1296 done 1297 1298 elif [ "$(uname -o)" == "Msys" ]; then 1299 if [[ ! -f "${key}" ]]; then 1300 exitError "Appsign key file was not found! (${key})" 1301 fi 1302 1303 logInfo "Using appsign key ${key}." 1304 IFS=$'\n' read -s -r -p "Key password: " password 1305 echo 1306 1307 for f in "${sign_files[@]}"; do 1308 ext=${f: -4} 1309 if [[ $ext == ".msi" || $ext == ".exe" || $ext == ".dll" ]]; then 1310 # Make sure we can find the signtool 1311 checkSigntoolCommandExists 1312 1313 # osslsigncode does not succeed at signing MSI files at this time... 1314 logInfo "Signing file '${f}' using Microsoft signtool..." 1315 signtool sign -f "${key}" -p "${password}" -d "KeePassXC" -td sha256 \ 1316 -fd sha256 -tr "${TIMESTAMP_SERVER}" "${f}" 1317 1318 if [ 0 -ne $? ]; then 1319 exitError "Signing failed!" 1320 fi 1321 else 1322 logInfo "Skipping non-executable file '${f}'..." 1323 fi 1324 done 1325 1326 else 1327 exitError "Unsupported platform for code signing!\n" 1328 fi 1329 1330 logInfo "All done!" 1331} 1332 1333# ----------------------------------------------------------------------- 1334# parse global command line 1335# ----------------------------------------------------------------------- 1336MODE="$1" 1337shift 1338if [ "" == "$MODE" ]; then 1339 logError "Missing arguments!\n" 1340 printUsage 1341 exit 1 1342elif [ "help" == "$MODE" ]; then 1343 printUsage "$1" 1344 exit 1345elif [ "check" == "$MODE" ] || [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] \ 1346 || [ "gpgsign" == "$MODE" ] || [ "appsign" == "$MODE" ] || [ "appimage" == "$MODE" ]; then 1347 ${MODE} "$@" 1348else 1349 printUsage "$MODE" 1350fi 1351