keepassxc

Форк
0
/
release-tool 
1471 строка · 47.2 Кб
1
#!/usr/bin/env bash
2
#
3
# KeePassXC Release Preparation Helper
4
# Copyright (C) 2021 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

19
printf "\e[1m\e[32mKeePassXC\e[0m Release Preparation Helper\n"
20
printf "Copyright (C) 2021 KeePassXC Team <https://keepassxc.org/>\n\n"
21

22
set -eE -o pipefail
23

24
if [ "$(uname -s)" == "Linux" ]; then
25
    OS_LINUX="1"
26
elif [ "$(uname -s)" == "Darwin" ]; then
27
    OS_MACOS="1"
28
elif [ "$(uname -o)" == "Msys" ]; then
29
    OS_WINDOWS="1"
30
fi
31

32
# -----------------------------------------------------------------------
33
#                        global default values
34
# -----------------------------------------------------------------------
35
RELEASE_NAME=""
36
APP_NAME="KeePassXC"
37
SRC_DIR="."
38
GPG_KEY="CFB4C2166397D0D2"
39
GPG_GIT_KEY=""
40
OUTPUT_DIR="release"
41
SOURCE_BRANCH=""
42
TAG_NAME=""
43
DOCKER_IMAGE=""
44
DOCKER_CONTAINER_NAME="keepassxc-build-container"
45
CMAKE_GENERATOR="Unix Makefiles"
46
CMAKE_OPTIONS=""
47
CPACK_GENERATORS="WIX;ZIP"
48
COMPILER="g++"
49
MAKE_OPTIONS="-j$(getconf _NPROCESSORS_ONLN)"
50
BUILD_PLUGINS="all"
51
INSTALL_PREFIX="/usr/local"
52
ORIG_BRANCH=""
53
ORIG_CWD="$(pwd)"
54
MACOSX_DEPLOYMENT_TARGET=10.15
55
TIMESTAMP_SERVER="http://timestamp.sectigo.com"
56

57
# -----------------------------------------------------------------------
58
#                          helper functions
59
# -----------------------------------------------------------------------
60
printUsage() {
61
    local cmd
62
    if [ -z "$1" ] || [ "help" == "$1" ]; then
63
        cmd="COMMAND"
64
    elif [ "check" == "$1" ] || [ "merge" == "$1" ] || [ "build" == "$1" ] || [ "gpgsign" == "$1" ] || \
65
        [ "appsign" == "$1" ] || [ "notarize" == "$1" ] || [ "appimage" == "$1" ] || [ "i18n" == "$1" ]; then
66
        cmd="$1"
67
    else
68
        logError "Unknown command: '$1'\n"
69
        cmd="COMMAND"
70
    fi
71

72
    printf "\e[1mUsage:\e[0m $(basename "$0") $cmd [OPTIONS, ...]\n"
73

74
    if [ "COMMAND" == "$cmd" ]; then
75
        cat << EOF
76

77
Commands:
78
  check      Perform a dry-run check, nothing is changed
79
  merge      Merge release branch into main branch and create release tags
80
  build      Build and package binary release from sources
81
  gpgsign    Sign previously compiled release packages with GPG
82
  appsign    Sign binaries with code signing certificates on Windows and macOS
83
  notarize   Submit macOS application DMG for notarization
84
  help       Show help for the given command
85
  i18n       Update translation files and pull from or push to Transifex
86
EOF
87
    elif [ "merge" == "$cmd" ]; then
88
        cat << EOF
89

90
Merge release branch into main branch and create release tags
91

92
Options:
93
  -v, --version        Release version number or name (required)
94
  -a, --app-name       Application name (default: '${APP_NAME}')
95
  -s, --source-dir     Source directory (default: '${SRC_DIR}')
96
  -k, --key            GPG key used to sign the merge commit and release tag,
97
                       leave empty to let Git choose your default key
98
                       (default: '${GPG_GIT_KEY}')
99
  -r, --release-branch Source release branch to merge from (default: 'release/VERSION')
100
  -t, --tag-name       Override release tag name (defaults to version number)
101
  -h, --help           Show this help
102
EOF
103
    elif [ "build" == "$cmd" ]; then
104
        cat << EOF
105

106
Build and package binary release from sources
107

108
Options:
109
  -v, --version           Release version number or name (required)
110
  -a, --app-name          Application name (default: '${APP_NAME}')
111
  -s, --source-dir        Source directory (default: '${SRC_DIR}')
112
  -o, --output-dir        Output directory where to build the release
113
                          (default: '${OUTPUT_DIR}')
114
  -t, --tag-name          Release tag to check out (defaults to version number)
115
  -b, --build             Build sources after exporting release
116
  -d, --docker-image      Use the specified Docker image to compile the application.
117
                          The image must have all required build dependencies installed.
118
                          This option has no effect if --build is not set.
119
      --container-name    Docker container name (default: '${DOCKER_CONTAINER_NAME}')
120
                          The container must not exist already
121
      --snapcraft         Create and use docker image to build snapcraft distribution.
122
                          This option has no effect if --docker-image is not set.
123
      --appimage          Build a Linux AppImage after compilation.
124
                          If this option is set, --install-prefix has no effect
125
      --appsign           Perform platform specific App Signing before packaging
126
      --timestamp         Explicitly set the timestamp server to use for appsign (default: '${TIMESTAMP_SERVER}')
127
      --vcpkg             Specify VCPKG toolchain file (example: ~/vcpkg/scripts/buildsystems/vcpkg.cmake)
128
  -k, --key               Specify the App Signing Key/Identity
129
      --cmake-generator   Override the default CMake generator (Default: Ninja)
130
  -c, --cmake-options     Additional CMake options for compiling the sources
131
      --compiler          Compiler to use (default: '${COMPILER}')
132
  -m, --make-options      Make options for compiling sources (default: '${MAKE_OPTIONS}')
133
  -g, --generators        Additional CPack generators (default: '${CPACK_GENERATORS}')
134
  -i, --install-prefix    Install prefix (default: '${INSTALL_PREFIX}')
135
  -p, --plugins           Space-separated list of plugins to build
136
                          (default: ${BUILD_PLUGINS})
137
      --snapshot          Don't checkout the release tag
138
  -n, --no-source-tarball Don't build source tarball
139
  -h, --help              Show this help
140
EOF
141
    elif [ "gpgsign" == "$cmd" ]; then
142
        cat << EOF
143

144
Sign previously compiled release packages with GPG
145

146
Options:
147
  -f, --files        Files to sign (required)
148
  -k, --key          GPG key used to sign the files (default: '${GPG_KEY}')
149
  -h, --help         Show this help
150
EOF
151
    elif [ "appsign" == "$cmd" ]; then
152
        cat << EOF
153

154
Sign binaries with code signing certificates on Windows and macOS
155

156
Options:
157
  -f, --files        Files to sign (required)
158
  -k, --key, -i, --identity
159
                     Signing Key or Apple Developer ID (required)
160
      --timestamp    Explicitly set the timestamp server to use for appsign (default: '${TIMESTAMP_SERVER}')
161
  -u, --username     Apple username for notarization (required on macOS)
162
  -h, --help         Show this help
163
EOF
164
    elif [ "notarize" == "$cmd" ]; then
165
        cat << EOF
166

167
Submit macOS application DMG for notarization
168

169
Options:
170
  -f, --files        Files to notarize (required)
171
  -u, --username     Apple username for notarization (required)
172
  -c, --keychain     Apple keychain entry name storing the notarization
173
                     app password (default: 'AC_PASSWORD')
174
  -h, --help         Show this help
175
EOF
176
    elif [ "appimage" == "$cmd" ]; then
177
        cat << EOF
178

179
Generate Linux AppImage from 'make install' AppDir
180

181
Options:
182
  -a, --appdir         Input AppDir (required)
183
  -v, --version        KeePassXC version
184
  -o, --output-dir     Output directory where to build the AppImage
185
                       (default: '${OUTPUT_DIR}')
186
  -d, --docker-image   Use the specified Docker image to build the AppImage.
187
                       The image must have all required build dependencies installed.
188
      --container-name Docker container name (default: '${DOCKER_CONTAINER_NAME}')
189
                       The container must not exist already
190
      --appsign        Embed a PGP signature into the AppImage
191
  -k, --key            The PGP Signing Key
192
      --verbosity      linuxdeploy verbosity (default: 3)
193
  -h, --help           Show this help
194
EOF
195
    elif [ "i18n" == "$cmd" ]; then
196
        cat << EOF
197

198
Update translation files and pull from or push to Transifex
199

200
Subcommands:
201
  tx-push      Push source translation file to Transifex
202
  tx-pull      Pull updated translations from Transifex
203
  lupdate      Update source translation file from C++ sources
204
EOF
205
    fi
206
}
207

208
logInfo() {
209
    printf "\e[1m[ \e[34mINFO\e[39m ]\e[0m $1\n"
210
}
211

212
logWarn() {
213
    printf "\e[1m[ \e[33mWARNING\e[39m ]\e[0m $1\n"
214
}
215

216
logError() {
217
    printf "\e[1m[ \e[31mERROR\e[39m ]\e[0m $1\n" >&2
218
}
219

220
init() {
221
    if [ -z "$RELEASE_NAME" ]; then
222
        logError "Missing arguments, --version is required!\n"
223
        printUsage "check"
224
        exit 1
225
    fi
226

227
    if [ -z "$TAG_NAME" ]; then
228
        TAG_NAME="$RELEASE_NAME"
229
    fi
230

231
    if [ -z "$SOURCE_BRANCH" ]; then
232
        SOURCE_BRANCH="release/${RELEASE_NAME}"
233
    fi
234

235
    ORIG_CWD="$(pwd)"
236
    SRC_DIR="$(realpath "$SRC_DIR")"
237
    cd "$SRC_DIR" > /dev/null 2>&1
238
    ORIG_BRANCH="$(git rev-parse --abbrev-ref HEAD 2> /dev/null)"
239
    cd "$ORIG_CWD"
240
}
241

242
cleanup() {
243
    logInfo "Checking out original branch..."
244
    if [ "" != "$ORIG_BRANCH" ]; then
245
        git checkout "$ORIG_BRANCH" > /dev/null 2>&1
246
    fi
247
    logInfo "Leaving source directory..."
248
    cd "$ORIG_CWD"
249
}
250

251
exitError() {
252
    cleanup
253
    logError "$1"
254
    exit 1
255
}
256

257
cmdExists() {
258
    command -v "$1" &> /dev/null
259
}
260

261
checkSourceDirExists() {
262
    if [ ! -d "$SRC_DIR" ]; then
263
        exitError "Source directory '${SRC_DIR}' does not exist!"
264
    fi
265
}
266

267
checkOutputDirDoesNotExist() {
268
    if [ -e "$OUTPUT_DIR" ]; then
269
        exitError "Output directory '$OUTPUT_DIR' already exists. Please choose a different location!"
270
    fi
271
}
272

273
checkGitRepository() {
274
    if [ ! -d .git ] || [ ! -f CHANGELOG.md ]; then
275
        exitError "Source directory is not a valid Git repository!"
276
    fi
277
}
278

279
checkReleaseDoesNotExist() {
280
    if [ $(git tag -l $TAG_NAME) ]; then
281
        exitError "Release '$RELEASE_NAME' (tag: '$TAG_NAME') already exists!"
282
    fi
283
}
284

285
checkWorkingTreeClean() {
286
    if ! git diff-index --quiet HEAD --; then
287
        exitError "Current working tree is not clean! Please commit or unstage any changes."
288
    fi
289
}
290

291
checkSourceBranchExists() {
292
    if ! git rev-parse "$SOURCE_BRANCH" > /dev/null 2>&1; then
293
        exitError "Source branch '$SOURCE_BRANCH' does not exist!"
294
    fi
295
}
296

297
checkVersionInCMake() {
298
    local app_name_upper="$(echo "$APP_NAME" | tr '[:lower:]' '[:upper:]')"
299
    local major_num="$(echo ${RELEASE_NAME} | cut -f1 -d.)"
300
    local minor_num="$(echo ${RELEASE_NAME} | cut -f2 -d.)"
301
    local patch_num="$(echo ${RELEASE_NAME} | cut -f3 -d. | cut -f1 -d-)"
302

303
    if ! grep -q "${app_name_upper}_VERSION_MAJOR \"${major_num}\"" CMakeLists.txt; then
304
        exitError "${app_name_upper}_VERSION_MAJOR not updated to '${major_num}' in CMakeLists.txt!"
305
    fi
306

307
    if ! grep -q "${app_name_upper}_VERSION_MINOR \"${minor_num}\"" CMakeLists.txt; then
308
        exitError "${app_name_upper}_VERSION_MINOR not updated to '${minor_num}' in CMakeLists.txt!"
309
    fi
310

311
    if ! grep -q "${app_name_upper}_VERSION_PATCH \"${patch_num}\"" CMakeLists.txt; then
312
        exitError "${app_name_upper}_VERSION_PATCH not updated to '${patch_num}' in CMakeLists.txt!"
313
    fi
314
}
315

316
checkChangeLog() {
317
    if [ ! -f CHANGELOG.md ]; then
318
        exitError "No CHANGELOG file found!"
319
    fi
320

321
    if ! grep -qEzo "## ${RELEASE_NAME} \([0-9]{4}-[0-9]{2}-[0-9]{2}\)" CHANGELOG.md; then
322
        exitError "'CHANGELOG.md' has not been updated to the '${RELEASE_NAME}' release!"
323
    fi
324
}
325

326
checkAppStreamInfo() {
327
    if [ ! -f share/linux/org.keepassxc.KeePassXC.appdata.xml ]; then
328
        exitError "No AppStream info file found!"
329
    fi
330

331
    if ! grep -qEzo "<release version=\"${RELEASE_NAME}\" date=\"[0-9]{4}-[0-9]{2}-[0-9]{2}\">" share/linux/org.keepassxc.KeePassXC.appdata.xml; then
332
        exitError "'share/linux/org.keepassxc.KeePassXC.appdata.xml' has not been updated to the '${RELEASE_NAME}' release!"
333
    fi
334
}
335

336
checkTransifexCommandExists() {
337
    if ! cmdExists tx; then
338
        exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'."
339
    fi
340
}
341

342
checkSigntoolCommandExists() {
343
    if ! cmdExists signtool; then
344
        exitError "signtool command not found on the PATH! Add the Windows SDK binary folder to your PATH."
345
    fi
346
}
347

348
checkXcodeSetup() {
349
    if ! cmdExists xcrun; then
350
        exitError "xcrun command not found on the PATH! Please check that you have correctly installed Xcode."
351
    fi
352
    if ! xcrun -f codesign > /dev/null 2>&1; then
353
        exitError "codesign command not found. You may need to run 'sudo xcode-select -r' to set up Xcode."
354
    fi
355
    if ! xcrun -f altool > /dev/null 2>&1; then
356
        exitError "altool command not found. You may need to run 'sudo xcode-select -r' to set up Xcode."
357
    fi
358
    if ! xcrun -f stapler > /dev/null 2>&1; then
359
        exitError "stapler command not found. You may need to run 'sudo xcode-select -r' to set up Xcode."
360
    fi
361
}
362

363
checkQt5LUpdateExists() {
364
    if cmdExists lupdate && ! $(lupdate -version | grep -q "lupdate version 5\."); then
365
        if ! cmdExists lupdate-qt5; then
366
            exitError "Qt Linguist tool (lupdate-qt5) is not installed! Please install using 'apt install qttools5-dev-tools'"
367
        fi
368
    fi
369
}
370

371
performChecks() {
372
    logInfo "Performing basic checks..."
373

374
    checkSourceDirExists
375

376
    logInfo "Changing to source directory..."
377
    cd "${SRC_DIR}"
378

379
    logInfo "Validating toolset and repository..."
380

381
    checkTransifexCommandExists
382
    checkQt5LUpdateExists
383
    checkGitRepository
384
    checkReleaseDoesNotExist
385
    checkWorkingTreeClean
386
    checkSourceBranchExists
387

388
    logInfo "Checking out '${SOURCE_BRANCH}'..."
389
    git checkout "$SOURCE_BRANCH" > /dev/null 2>&1
390

391
    logInfo "Attempting to find '${RELEASE_NAME}' in various files..."
392

393
    checkVersionInCMake
394
    checkChangeLog
395
    checkAppStreamInfo
396

397
    logInfo "\e[1m\e[32mAll checks passed!\e[0m"
398
}
399

400
# re-implement realpath for OS X (thanks mschrag)
401
# https://superuser.com/questions/205127/
402
if ! cmdExists realpath; then
403
    realpath() {
404
        pushd . > /dev/null
405
        if [ -d "$1" ]; then
406
            cd "$1"
407
            dirs -l +0
408
        else
409
            cd "$(dirname "$1")"
410
            cur_dir=$(dirs -l +0)
411

412
            if [ "$cur_dir" == "/" ]; then
413
                echo "$cur_dir$(basename "$1")"
414
            else
415
                echo "$cur_dir/$(basename "$1")"
416
            fi
417
        fi
418
        popd > /dev/null
419
    }
420
fi
421

422

423
trap 'exitError "Exited upon user request."' SIGINT SIGTERM
424
trap 'exitError "Error occurred!"' ERR
425

426

427
# -----------------------------------------------------------------------
428
#                             check command
429
# -----------------------------------------------------------------------
430
check() {
431
    while [ $# -ge 1 ]; do
432
        local arg="$1"
433
        case "$arg" in
434
            -v|--version)
435
                RELEASE_NAME="$2"
436
                shift ;;
437
        esac
438
        shift
439
    done
440

441
    init
442

443
    performChecks
444

445
    cleanup
446

447
    logInfo "Congrats! You can successfully merge, build, and sign KeepassXC."
448
}
449

450
# -----------------------------------------------------------------------
451
#                             merge command
452
# -----------------------------------------------------------------------
453
merge() {
454
    while [ $# -ge 1 ]; do
455
        local arg="$1"
456
        case "$arg" in
457
            -v|--version)
458
                RELEASE_NAME="$2"
459
                shift ;;
460

461
            -a|--app-name)
462
                APP_NAME="$2"
463
                shift ;;
464

465
            -s|--source-dir)
466
                SRC_DIR="$2"
467
                shift ;;
468

469
            -k|--key|-g|--gpg-key)
470
                GPG_GIT_KEY="$2"
471
                shift ;;
472

473
            --timestamp)
474
                TIMESTAMP_SERVER="$2"
475
                shift ;;
476

477
            -r|--release-branch)
478
                SOURCE_BRANCH="$2"
479
                shift ;;
480

481
            -t|--tag-name)
482
                TAG_NAME="$2"
483
                shift ;;
484

485
            -h|--help)
486
                printUsage "merge"
487
                exit ;;
488

489
            *)
490
                logError "Unknown option '$arg'\n"
491
                printUsage "merge"
492
                exit 1 ;;
493
        esac
494
        shift
495
    done
496

497
    init
498

499
    performChecks
500

501
    # Update translations
502
    i18n lupdate
503
    i18n tx-pull
504

505
    if [ 0 -ne $? ]; then
506
        exitError "Updating translations failed!"
507
    fi
508
    if ! git diff-index --quiet HEAD --; then
509
        git add -A ./share/translations/
510
        logInfo "Committing changes..."
511
        if [ -z "$GPG_GIT_KEY" ]; then
512
            git commit -m "Update translations"
513
        else
514
            git commit -m "Update translations" -S"$GPG_GIT_KEY"
515
        fi
516
    fi
517

518
    local flags="-Pzo"
519
    if [ -n "$OS_MACOS" ]; then
520
        flags="-Ezo"
521
    fi
522
    CHANGELOG=$(grep ${flags} "## ${RELEASE_NAME} \([0-9]{4}-[0-9]{2}-[0-9]{2}\)\n\n(.|\n)+?\n\n## " CHANGELOG.md \
523
                | tail -n+3 | sed '$d' | sed 's/^### //')
524
    COMMIT_MSG="Release ${RELEASE_NAME}"
525

526
    logInfo "Creating tag '${TAG_NAME}'..."
527
    if [ -z  "$GPG_GIT_KEY" ]; then
528
        git tag -a "$TAG_NAME" -m "$COMMIT_MSG" -m "${CHANGELOG}" -s
529
    else
530
        git tag -a "$TAG_NAME" -m "$COMMIT_MSG" -m "${CHANGELOG}" -s -u "$GPG_GIT_KEY"
531
    fi
532

533
    logInfo "Advancing 'latest' tag..."
534
    if [ -z  "$GPG_GIT_KEY" ]; then
535
        git tag -sf -a "latest" -m "Latest stable release"
536
    else
537
        git tag -sf -u "$GPG_GIT_KEY" -a "latest" -m "Latest stable release"
538
    fi
539

540
    cleanup
541

542
    logInfo "All done!"
543
    logInfo "Don't forget to push the tags using \e[1mgit push --tags\e[0m."
544
}
545

546
# -----------------------------------------------------------------------
547
#                         appimage command
548
# -----------------------------------------------------------------------
549
appimage() {
550
    local appdir
551
    local build_appsign=false
552
    local build_key
553
    local verbosity="1"
554

555
    while [ $# -ge 1 ]; do
556
        local arg="$1"
557
        case "$arg" in
558
            -v|--version)
559
                RELEASE_NAME="$2"
560
                shift ;;
561

562
            -a|--appdir)
563
                appdir="$2"
564
                shift ;;
565

566
            -o|--output-dir)
567
                OUTPUT_DIR="$2"
568
                shift ;;
569

570
            -d|--docker-image)
571
                DOCKER_IMAGE="$2"
572
                shift ;;
573

574
            --container-name)
575
                DOCKER_CONTAINER_NAME="$2"
576
                shift ;;
577

578
            --appsign)
579
                build_appsign=true ;;
580

581
            --verbosity)
582
                verbosity=$2
583
                shift ;;
584

585
            -k|--key)
586
                build_key="$2"
587
                shift ;;
588

589
            -h|--help)
590
                printUsage "appimage"
591
                exit ;;
592

593
            *)
594
                logError "Unknown option '$arg'\n"
595
                printUsage "appimage"
596
                exit 1 ;;
597
        esac
598
        shift
599
    done
600

601
    if [ -z "${appdir}" ]; then
602
        logError "Missing arguments, --appdir is required!\n"
603
        printUsage "appimage"
604
        exit 1
605
    fi
606

607
    if [ ! -d "${appdir}" ]; then
608
        exitError "AppDir does not exist, please create one with 'make install'!"
609
    elif [ -e "${appdir}/AppRun" ]; then
610
        exitError "AppDir has already been run through linuxdeploy, please create a fresh AppDir with 'make install'."
611
    fi
612

613
    appdir="$(realpath "$appdir")"
614

615
    local out="${OUTPUT_DIR}"
616
    if [ -z "$out" ]; then
617
        out="."
618
    fi
619
    mkdir -p "$out"
620
    local out_real="$(realpath "$out")"
621
    cd "$out"
622

623
    local linuxdeploy="linuxdeploy"
624
    local linuxdeploy_cleanup
625
    local linuxdeploy_plugin_qt="linuxdeploy-plugin-qt"
626
    local linuxdeploy_plugin_qt_cleanup
627
    local appimagetool="appimagetool"
628
    local appimagetool_cleanup
629

630
    logInfo "Testing for AppImage tools..."
631
    local docker_test_cmd
632
    if [ "" != "$DOCKER_IMAGE" ]; then
633
        docker_test_cmd="docker run -it --user $(id -u):$(id -g) --rm ${DOCKER_IMAGE}"
634
    fi
635

636
    # Test if linuxdeploy and linuxdeploy-plugin-qt are installed
637
    # on the system or inside the Docker container
638
    if ! ${docker_test_cmd} which ${linuxdeploy} > /dev/null; then
639
        logInfo "Downloading linuxdeploy..."
640
        linuxdeploy="./linuxdeploy"
641
        linuxdeploy_cleanup="rm -f ${linuxdeploy}"
642
        if ! curl -Lf "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" > "$linuxdeploy"; then
643
            exitError "linuxdeploy download failed."
644
        fi
645
        chmod +x "$linuxdeploy"
646
    fi
647
    if ! ${docker_test_cmd} which ${linuxdeploy_plugin_qt} > /dev/null; then
648
        logInfo "Downloading linuxdeploy-plugin-qt..."
649
        linuxdeploy_plugin_qt="./linuxdeploy-plugin-qt"
650
        linuxdeploy_plugin_qt_cleanup="rm -f ${linuxdeploy_plugin_qt}"
651
        if ! curl -Lf "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" > "$linuxdeploy_plugin_qt"; then
652
            exitError "linuxdeploy-plugin-qt download failed."
653
        fi
654
        chmod +x "$linuxdeploy_plugin_qt"
655
    fi
656

657
    # appimagetool is always run outside a Docker container, so we can access our GPG keys
658
    if ! cmdExists ${appimagetool}; then
659
        logInfo "Downloading appimagetool..."
660
        appimagetool="./appimagetool"
661
        appimagetool_cleanup="rm -f ${appimagetool}"
662
        if ! curl -Lf "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" > "$appimagetool"; then
663
            exitError "appimagetool download failed."
664
        fi
665
        chmod +x "$appimagetool"
666
    fi
667

668
    # Create custom AppRun wrapper
669
    cat << 'EOF' > "${out_real}/KeePassXC-AppRun"
670
#!/usr/bin/env bash
671

672
export PATH="$(dirname $0)/usr/bin:${PATH}"
673
export LD_LIBRARY_PATH="$(dirname $0)/usr/lib:${LD_LIBRARY_PATH}"
674

675
if [ "$1" == "cli" ]; then
676
    shift
677
    exec keepassxc-cli "$@"
678
elif [ "$1" == "proxy" ]; then
679
    shift
680
    exec keepassxc-proxy "$@"
681
elif [ -v CHROME_WRAPPER ] || [ -v MOZ_LAUNCHED_CHILD ]; then
682
    exec keepassxc-proxy "$@"
683
else
684
    exec keepassxc "$@"
685
fi
686
EOF
687
    chmod +x "${out_real}/KeePassXC-AppRun"
688

689
    # Find .desktop files, icons, and binaries to deploy
690
    local desktop_file="$(find "$appdir" -name "org.keepassxc.KeePassXC.desktop" | head -n1)"
691
    local icon="$(find "$appdir" -path '*/application/256x256/apps/keepassxc.png' | head -n1)"
692
    local executables="$(find "$appdir" -type f -executable -path '*/bin/keepassxc*' -print0 | xargs -0 -i printf " --executable={}")"
693

694
    logInfo "Collecting libs and patching binaries..."
695
    if [ -z "$DOCKER_IMAGE" ]; then
696
        "$linuxdeploy" --verbosity=${verbosity} --plugin=qt --appdir="$appdir" --desktop-file="$desktop_file" \
697
        --custom-apprun="${out_real}/KeePassXC-AppRun" --icon-file="$icon" ${executables}
698
    else
699
        docker run --name "$DOCKER_CONTAINER_NAME" --rm \
700
            --cap-add SYS_ADMIN --security-opt apparmor:unconfined --device /dev/fuse -it \
701
            -v "${out_real}:${out_real}:rw" \
702
            -v "${appdir}:${appdir}:rw" \
703
            -w "$out_real" \
704
            --user $(id -u):$(id -g) \
705
            "$DOCKER_IMAGE" \
706
            bash -c "${linuxdeploy} --verbosity=${verbosity} --plugin=qt \
707
            --appdir='${appdir}' --custom-apprun='${out_real}/KeePassXC-AppRun' \
708
            --desktop-file='${desktop_file}' --icon-file='${icon}' ${executables}"
709
    fi
710

711
    if [ $? -ne 0 ]; then
712
        exitError "AppDir deployment failed."
713
    fi
714

715
    logInfo "Creating AppImage..."
716
    local appsign_flag=""
717
    local appsign_key_flag=""
718
    if ${build_appsign}; then
719
        appsign_flag="--sign"
720
        appsign_key_flag="--sign-key ${build_key}"
721
    fi
722
    local appimage_name="KeePassXC-x86_64.AppImage"
723
    if [ "" != "$RELEASE_NAME" ]; then
724
        appimage_name="KeePassXC-${RELEASE_NAME}-x86_64.AppImage"
725
        echo "X-AppImage-Version=${RELEASE_NAME}" >> "$desktop_file"
726
    fi
727

728
    # Run appimagetool to package (and possibly sign) AppImage
729
    # --no-appstream is required, since it may crash on newer systems
730
    # see: https://github.com/AppImage/AppImageKit/issues/856
731
    if ! "$appimagetool" --updateinformation "gh-releases-zsync|keepassxreboot|keepassxc|latest|KeePassXC-*-x86_64.AppImage.zsync" \
732
        ${appsign_flag} ${appsign_key_flag} --no-appstream "$appdir" "${out_real}/${appimage_name}"; then
733
        exitError "AppImage creation failed."
734
    fi
735

736
    logInfo "Cleaning up temporary files..."
737
    ${linuxdeploy_cleanup}
738
    ${linuxdeploy_plugin_qt_cleanup}
739
    ${appimagetool_cleanup}
740
    rm -f "${out_real}/KeePassXC-AppRun"
741
}
742

743
# -----------------------------------------------------------------------
744
#                             build command
745
# -----------------------------------------------------------------------
746
build() {
747
    local build_source_tarball=true
748
    local build_snapshot=false
749
    local build_snapcraft=false
750
    local build_appimage=false
751
    local build_generators=""
752
    local build_appsign=false
753
    local build_key=""
754
    local build_vcpkg=""
755

756
    while [ $# -ge 1 ]; do
757
        local arg="$1"
758
        case "$arg" in
759
            -v|--version)
760
                RELEASE_NAME="$2"
761
                shift ;;
762

763
            -a|--app-name)
764
                APP_NAME="$2"
765
                shift ;;
766

767
            -s|--source-dir)
768
                SRC_DIR="$2"
769
                shift ;;
770

771
            -o|--output-dir)
772
                OUTPUT_DIR="$2"
773
                shift ;;
774

775
            -t|--tag-name)
776
                TAG_NAME="$2"
777
                shift ;;
778

779
            -d|--docker-image)
780
                DOCKER_IMAGE="$2"
781
                shift ;;
782

783
            --container-name)
784
                DOCKER_CONTAINER_NAME="$2"
785
                shift ;;
786

787
            --appsign)
788
                build_appsign=true ;;
789

790
            --timestamp)
791
                TIMESTAMP_SERVER="$2"
792
                shift ;;
793

794
            -k|--key)
795
                build_key="$2"
796
                shift ;;
797

798
            --snapcraft)
799
                build_snapcraft=true ;;
800

801
            --appimage)
802
                build_appimage=true ;;
803

804
            --cmake-generator)
805
                CMAKE_GENERATOR="$2"
806
                shift ;;
807

808
            -c|--cmake-options)
809
                CMAKE_OPTIONS="$2"
810
                shift ;;
811

812
            --compiler)
813
                COMPILER="$2"
814
                shift ;;
815

816
            --vcpkg)
817
                build_vcpkg="$2"
818
                shift ;;
819

820
            -m|--make-options)
821
                MAKE_OPTIONS="$2"
822
                shift ;;
823

824
            -g|--generators)
825
                build_generators="$2"
826
                shift ;;
827

828
            -i|--install-prefix)
829
                INSTALL_PREFIX="$2"
830
                shift ;;
831

832
            -p|--plugins)
833
                BUILD_PLUGINS="$2"
834
                shift ;;
835

836
            -n|--no-source-tarball)
837
                build_source_tarball=false ;;
838

839
            --snapshot)
840
                build_snapshot=true ;;
841

842
            -h|--help)
843
                printUsage "build"
844
                exit ;;
845

846
            *)
847
                logError "Unknown option '$arg'\n"
848
                printUsage "build"
849
                exit 1 ;;
850
        esac
851
        shift
852
    done
853

854
    init
855

856
    # Resolve appsign key to absolute path if under Windows
857
    if [[ "${build_key}" && -n "$OS_WINDOWS" ]]; then
858
        build_key="$(realpath "${build_key}")"
859
    fi
860

861
    if [[ -f ${build_vcpkg} ]]; then
862
        CMAKE_OPTIONS="${CMAKE_OPTIONS} -DCMAKE_TOOLCHAIN_FILE=${build_vcpkg} -DX_VCPKG_APPLOCAL_DEPS_INSTALL=ON"
863
    fi
864

865
    if ${build_snapshot}; then
866
        TAG_NAME="HEAD"
867
        local branch=`git rev-parse --abbrev-ref HEAD`
868
        logInfo "Using current branch ${branch} to build..."
869
        RELEASE_NAME="${RELEASE_NAME}-snapshot"
870
        CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Snapshot -DOVERRIDE_VERSION=${RELEASE_NAME}"
871
    else
872
        checkWorkingTreeClean
873
        if echo "$TAG_NAME" | grep -qE '\-(alpha|beta)[0-9]+$'; then
874
            CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=PreRelease"
875
            logInfo "Checking out pre-release tag '${TAG_NAME}'..."
876
        else
877
            CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Release"
878
            logInfo "Checking out release tag '${TAG_NAME}'..."
879
        fi
880

881
        if ! git checkout "$TAG_NAME" > /dev/null 2>&1; then
882
            exitError "Failed to check out target branch."
883
        fi
884
    fi
885

886
    if  ! ${build_snapshot} && [ -d "$OUTPUT_DIR" ]; then
887
        exitError "Output dir '${OUTPUT_DIR}' already exists."
888
    fi
889

890
    logInfo "Creating output directory..."
891
    if ! mkdir -p "$OUTPUT_DIR"; then
892
        exitError "Failed to create output directory!"
893
    fi
894
    OUTPUT_DIR="$(realpath "$OUTPUT_DIR")"
895

896
    if ${build_source_tarball}; then
897
        logInfo "Creating source tarball..."
898
        local app_name_lower="$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]')"
899
        local prefix="${app_name_lower}-${RELEASE_NAME}"
900
        local tarball_name="${prefix}-src.tar"
901

902
        git archive --format=tar "$TAG_NAME" --prefix="${prefix}/" --output="${OUTPUT_DIR}/${tarball_name}"
903

904
        # add .version and .gitrev files to tarball
905
        mkdir "${prefix}"
906
        echo -n ${RELEASE_NAME} > "${prefix}/.version"
907
        echo -n `git rev-parse --short=7 HEAD` > "${prefix}/.gitrev"
908
        tar --append --file="${OUTPUT_DIR}/${tarball_name}" "${prefix}/.version" "${prefix}/.gitrev"
909
        rm "${prefix}/.version" "${prefix}/.gitrev"
910
        rmdir "${prefix}" 2> /dev/null
911

912
        local xz="xz"
913
        if ! cmdExists xz; then
914
            logWarn "xz not installed. Falling back to bz2..."
915
            xz="bzip2"
916
        fi
917
        $xz -6 -f "${OUTPUT_DIR}/${tarball_name}"
918
    fi
919

920
    logInfo "Creating build directory..."
921
    mkdir -p "${OUTPUT_DIR}/build-release"
922
    cd "${OUTPUT_DIR}/build-release"
923

924
    logInfo "Configuring sources..."
925
    for p in ${BUILD_PLUGINS}; do
926
        CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_XC_$(echo $p | tr '[:lower:]' '[:upper:]')=On"
927
    done
928
    if [ -n "$OS_LINUX" ] && ${build_appimage}; then
929
        CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_DIST_TYPE=AppImage"
930
        # linuxdeploy requires /usr as install prefix
931
        INSTALL_PREFIX="/usr"
932
    fi
933
    if [ -n "$OS_MACOS" ]; then
934
      type brew &> /dev/null 2>&1
935
      if [ $? -eq 0 ]; then
936
        INSTALL_PREFIX=$(brew --prefix)
937
      fi
938
    fi
939

940
    # Do not build tests cases
941
    CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_TESTS=OFF"
942

943
    if [ "$COMPILER" == "g++" ]; then
944
        export CC=gcc
945
    elif [ "$COMPILER" == "clang++" ]; then
946
        export CC=clang
947
    else
948
        export CC="$COMPILER"
949
    fi
950
    export CXX="$COMPILER"
951

952
    if [ -z "$DOCKER_IMAGE" ]; then
953
        if [ -n "$OS_MACOS" ]; then
954
            # Building on macOS
955
            export MACOSX_DEPLOYMENT_TARGET
956

957
            logInfo "Configuring build..."
958
            cmake -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="$(uname -m)" \
959
            -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" ${CMAKE_OPTIONS} "$SRC_DIR"
960

961
            logInfo "Compiling and packaging sources..."
962
            cmake --build . -- ${MAKE_OPTIONS}
963
            cpack -G "DragNDrop"
964

965
            # Appsign the executables if desired
966
            if ${build_appsign}; then
967
                logInfo "Signing executable files"
968
                appsign "-f" "./${APP_NAME}-${RELEASE_NAME}.dmg" "-k" "${build_key}"
969
            fi
970

971
            mv "./${APP_NAME}-${RELEASE_NAME}.dmg" "../${APP_NAME}-${RELEASE_NAME}-$(uname -m).dmg"
972
        elif [ -n "$OS_WINDOWS" ]; then
973
            # Building on Windows with Msys2
974
            logInfo "Configuring build..."
975
            cmake -DCMAKE_BUILD_TYPE=Release -G "${CMAKE_GENERATOR}" -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \
976
                ${CMAKE_OPTIONS} "$SRC_DIR"
977

978
            logInfo "Compiling and packaging sources..."
979
            cmake --build . --config "Release" -- ${MAKE_OPTIONS}
980

981
            # Appsign the executables if desired
982
            if ${build_appsign} && [ -f "${build_key}" ]; then
983
                logInfo "Signing executable files"
984
                appsign "-f" $(find src | grep -Ei 'keepassxc.*(\.exe|\.dll)$') "-k" "${build_key}"
985
            fi
986

987
            # Call cpack directly instead of calling make package.
988
            # This is important because we want to build the MSI when making a
989
            # release.
990
            cpack -G "${CPACK_GENERATORS};${build_generators}"
991

992
            mv "${APP_NAME}-"*.* ../
993
        else
994
            mkdir -p "${OUTPUT_DIR}/KeePassXC.AppDir"
995

996
            # Building on Linux without Docker container
997
            logInfo "Configuring build..."
998
            cmake -DCMAKE_BUILD_TYPE=Release ${CMAKE_OPTIONS} \
999
                -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" "$SRC_DIR"
1000

1001
            logInfo "Compiling sources..."
1002
            make ${MAKE_OPTIONS}
1003

1004
            logInfo "Installing to bin dir..."
1005
            make DESTDIR="${OUTPUT_DIR}/KeePassXC.AppDir" install/strip
1006
        fi
1007
    else
1008
        if ${build_snapcraft}; then
1009
            logInfo "Building snapcraft docker image..."
1010

1011
            sudo docker image build -t "$DOCKER_IMAGE" "$(realpath "$SRC_DIR")/ci/snapcraft"
1012

1013
            logInfo "Launching Docker contain to compile snapcraft..."
1014

1015
            sudo docker run --name "$DOCKER_CONTAINER_NAME" --rm -it --user $(id -u):$(id -g) \
1016
                -v "$(realpath "$SRC_DIR"):/keepassxc" -w "/keepassxc" \
1017
                "$DOCKER_IMAGE" snapcraft
1018
        else
1019
            mkdir -p "${OUTPUT_DIR}/KeePassXC.AppDir"
1020

1021
            logInfo "Launching Docker container to compile sources..."
1022

1023
            docker run --name "$DOCKER_CONTAINER_NAME" --rm \
1024
                --cap-add SYS_ADMIN --security-opt apparmor:unconfined --device /dev/fuse \
1025
                --user $(id -u):$(id -g) \
1026
                -e "CC=${CC}" -e "CXX=${CXX}" -it  \
1027
                -v "$(realpath "$SRC_DIR"):/keepassxc/src:ro" \
1028
                -v "$(realpath "$OUTPUT_DIR"):/keepassxc/out:rw" \
1029
                "$DOCKER_IMAGE" \
1030
                bash -c "cd /keepassxc/out/build-release && \
1031
                    cmake -DCMAKE_BUILD_TYPE=Release ${CMAKE_OPTIONS} \
1032
                        -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} /keepassxc/src && \
1033
                    make ${MAKE_OPTIONS} && make DESTDIR=/keepassxc/out/KeePassXC.AppDir install/strip"
1034
        fi
1035

1036
        if [ 0 -ne $? ]; then
1037
            exitError "Docker build failed!"
1038
        fi
1039

1040
        logInfo "Build finished, Docker container terminated."
1041
    fi
1042

1043
    if [ -n "$OS_LINUX" ] && ${build_appimage}; then
1044
        local appsign_flag=""
1045
        local appsign_key_flag=""
1046
        local docker_image_flag=""
1047
        local docker_container_name_flag=""
1048
        if ${build_appsign}; then
1049
            appsign_flag="--appsign"
1050
            appsign_key_flag="-k ${build_key}"
1051
        fi
1052
        if [ "" != "${DOCKER_IMAGE}" ]; then
1053
            docker_image_flag="-d ${DOCKER_IMAGE}"
1054
            docker_container_name_flag="--container-name ${DOCKER_CONTAINER_NAME}"
1055
        fi
1056
        appimage -a "${OUTPUT_DIR}/KeePassXC.AppDir" -o "${OUTPUT_DIR}" \
1057
            ${appsign_flag} ${appsign_key_flag} ${docker_image_flag} ${docker_container_name_flag}
1058
    fi
1059

1060
    cleanup
1061

1062
    logInfo "All done!"
1063
}
1064

1065
# -----------------------------------------------------------------------
1066
#                           gpgsign command
1067
# -----------------------------------------------------------------------
1068
gpgsign() {
1069
    local sign_files=()
1070

1071
    while [ $# -ge 1 ]; do
1072
        local arg="$1"
1073
        case "$arg" in
1074
            -f|--files)
1075
                while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
1076
                    sign_files+=("$2")
1077
                    shift
1078
                done ;;
1079

1080
            -k|--key|-g|--gpg-key)
1081
                GPG_KEY="$2"
1082
                shift ;;
1083

1084
            -h|--help)
1085
                printUsage "gpgsign"
1086
                exit ;;
1087

1088
            *)
1089
                logError "Unknown option '$arg'\n"
1090
                printUsage "gpgsign"
1091
                exit 1 ;;
1092
        esac
1093
        shift
1094
    done
1095

1096
    if [ -z "${sign_files}" ]; then
1097
        logError "Missing arguments, --files is required!\n"
1098
        printUsage "gpgsign"
1099
        exit 1
1100
    fi
1101

1102
    for f in "${sign_files[@]}"; do
1103
        if [ ! -f "$f" ]; then
1104
            exitError "File '${f}' does not exist or is not a file!"
1105
        fi
1106

1107
        logInfo "Signing file '${f}' using release key..."
1108
        gpg --output "${f}.sig" --armor --local-user "$GPG_KEY" --detach-sig "$f"
1109

1110
        if [ 0 -ne $? ]; then
1111
            exitError "Signing failed!"
1112
        fi
1113

1114
        logInfo "Creating digest for file '${f}'..."
1115
        local rp="$(realpath "$f")"
1116
        local bname="$(basename "$f")"
1117
        (cd "$(dirname "$rp")"; sha256sum "$bname"  > "${bname}.DIGEST")
1118
    done
1119

1120
    logInfo "All done!"
1121
}
1122

1123
# -----------------------------------------------------------------------
1124
#                           appsign command
1125
# -----------------------------------------------------------------------
1126
appsign() {
1127
    local sign_files=()
1128
    local key
1129

1130
    while [ $# -ge 1 ]; do
1131
        local arg="$1"
1132
        case "$arg" in
1133
            -f|--files)
1134
                while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
1135
                    sign_files+=("$2")
1136
                    shift
1137
                done ;;
1138

1139
            -k|--key|-i|--identity)
1140
                key="$2"
1141
                shift ;;
1142

1143
            -h|--help)
1144
                printUsage "appsign"
1145
                exit ;;
1146

1147
            *)
1148
                logError "Unknown option '$arg'\n"
1149
                printUsage "appsign"
1150
                exit 1 ;;
1151
        esac
1152
        shift
1153
    done
1154

1155
    if [ -z "${key}" ]; then
1156
        logError "Missing arguments, --key is required!\n"
1157
        printUsage "appsign"
1158
        exit 1
1159
    fi
1160

1161
    if [ -z "${sign_files}" ]; then
1162
        logError "Missing arguments, --files is required!\n"
1163
        printUsage "appsign"
1164
        exit 1
1165
    fi
1166

1167
    for f in "${sign_files[@]}"; do
1168
        if [ ! -e "${f}" ]; then
1169
            exitError "File '${f}' does not exist!"
1170
        fi
1171
    done
1172

1173
    if [ -n "$OS_MACOS" ]; then
1174
        checkXcodeSetup
1175

1176
        local orig_dir="$(pwd)"
1177
        local real_src_dir="$(realpath "${SRC_DIR}")"
1178
        for f in "${sign_files[@]}"; do
1179
            if [[ ${f: -4} == '.dmg' ]]; then
1180
                logInfo "Unpacking disk image '${f}'..."
1181
                local tmp_dir="/tmp/KeePassXC_${RANDOM}"
1182
                mkdir -p ${tmp_dir}/mnt
1183
                if ! hdiutil attach -quiet -noautoopen -mountpoint ${tmp_dir}/mnt "${f}"; then
1184
                    exitError "DMG mount failed!"
1185
                fi
1186
                cd ${tmp_dir}
1187
                cp -a ./mnt ./app
1188
                hdiutil detach -quiet ${tmp_dir}/mnt
1189
                local app_dir_tmp="./app/KeePassXC.app"
1190

1191
                if [ ! -d "$app_dir_tmp" ]; then
1192
                    cd "${orig_dir}"
1193
                    exitError "Unpacking failed!"
1194
                fi
1195
            elif [[ ${f: -4} == '.app' ]]; then
1196
                local app_dir_tmp="$f"
1197
            else
1198
                logWarn "Skipping non-app file '${f}'..."
1199
                continue
1200
            fi
1201

1202
            logInfo "Signing libraries and frameworks..."
1203
            if ! find "$app_dir_tmp" \( -name '*.dylib' -o -name '*.so' -o -name '*.framework' \) -print0 | xargs -0 \
1204
                xcrun codesign --sign "${key}" --verbose --force --options runtime; then
1205
                cd "${orig_dir}"
1206
                exitError "Signing failed!"
1207
            fi
1208
            logInfo "Signing executables..."
1209
            if ! find "${app_dir_tmp}/Contents/MacOS" \( -type f -not -name KeePassXC \) -print0  | xargs -0 \
1210
                xcrun codesign --sign "${key}" --verbose --force --options runtime; then
1211
                cd "${orig_dir}"
1212
                exitError "Signing failed!"
1213
            fi
1214
            # Sign main executable with additional entitlements
1215
            if ! xcrun codesign --sign "${key}" --verbose --force --options runtime --entitlements \
1216
                "${real_src_dir}/share/macosx/keepassxc.entitlements" "${app_dir_tmp}/Contents/MacOS/KeePassXC"; then
1217
                cd "${orig_dir}"
1218
                exitError "Signing failed!"
1219
            fi
1220

1221
            if [[ ${f: -4} == '.dmg' ]]; then
1222
                logInfo "Repacking disk image..."
1223
                hdiutil create \
1224
                    -volname "KeePassXC" \
1225
                    -size $((1000 * ($(du -sk ./app | cut -f1) + 5000))) \
1226
                    -srcfolder ./app \
1227
                    -fs HFS+ \
1228
                    -fsargs "-c c=64,a=16,e=16" \
1229
                    -format UDBZ \
1230
                    "${tmp_dir}/$(basename "${f}")"
1231

1232
                cd "${orig_dir}"
1233
                cp -f "${tmp_dir}/$(basename "${f}")" "${f}"
1234
                rm -Rf ${tmp_dir}
1235
            fi
1236

1237
            logInfo "File '${f}' successfully signed."
1238
        done
1239

1240
    elif [ -n "$OS_WINDOWS" ]; then
1241
        if [[ ! -f "${key}" ]]; then
1242
            exitError "Appsign key file was not found! (${key})"
1243
        fi
1244

1245
        logInfo "Using appsign key ${key}."
1246
        IFS=$'\n' read -s -r -p "Key password: " password
1247
        echo
1248

1249
        for f in "${sign_files[@]}"; do
1250
            ext=${f: -4}
1251
            if [[ $ext == ".msi" || $ext == ".exe" || $ext == ".dll" ]]; then
1252
                # Make sure we can find the signtool
1253
                checkSigntoolCommandExists
1254

1255
                # osslsigncode does not succeed at signing MSI files at this time...
1256
                logInfo "Signing file '${f}' using Microsoft signtool..."
1257
                signtool sign -f "${key}" -p "${password}" -d "KeePassXC" -td sha256 \
1258
                    -fd sha256 -tr "${TIMESTAMP_SERVER}" "${f}"
1259

1260
                if [ 0 -ne $? ]; then
1261
                    exitError "Signing failed!"
1262
                fi
1263
            else
1264
                logInfo "Skipping non-executable file '${f}'..."
1265
            fi
1266
        done
1267

1268
    else
1269
        exitError "Unsupported platform for code signing!\n"
1270
    fi
1271

1272
    logInfo "All done!"
1273
}
1274

1275

1276
# -----------------------------------------------------------------------
1277
#                           notarize command
1278
# -----------------------------------------------------------------------
1279
notarize() {
1280
    local notarize_files=()
1281
    local ac_username
1282
    local ac_keychain="AC_PASSWORD"
1283

1284
    while [ $# -ge 1 ]; do
1285
        local arg="$1"
1286
        case "$arg" in
1287
            -f|--files)
1288
                while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
1289
                    notarize_files+=("$2")
1290
                    shift
1291
                done ;;
1292

1293
            -u|--username)
1294
                ac_username="$2"
1295
                shift ;;
1296

1297
            -c|--keychain)
1298
                ac_keychain="$2"
1299
                shift ;;
1300

1301
            -h|--help)
1302
                printUsage "notarize"
1303
                exit ;;
1304

1305
            *)
1306
                logError "Unknown option '$arg'\n"
1307
                printUsage "notarize"
1308
                exit 1 ;;
1309
        esac
1310
        shift
1311
    done
1312

1313
    if [ -z "$OS_MACOS" ]; then
1314
        exitError "Notarization is only supported on macOS!"
1315
    fi
1316

1317
    if [ -z "${notarize_files}" ]; then
1318
        logError "Missing arguments, --files is required!\n"
1319
        printUsage "notarize"
1320
        exit 1
1321
    fi
1322

1323
    if [ -z "$ac_username" ]; then
1324
        logError "Missing arguments, --username is required!"
1325
        printUsage "notarize"
1326
        exit 1
1327
    fi
1328

1329
    for f in "${notarize_files[@]}"; do
1330
        if [[ ${f: -4} != '.dmg' ]]; then
1331
            logWarn "Skipping non-DMG file '${f}'..."
1332
            continue
1333
        fi
1334

1335
        logInfo "Submitting disk image '${f}' for notarization..."
1336
        local status
1337
        status="$(xcrun altool --notarize-app \
1338
            --primary-bundle-id "org.keepassxc.keepassxc" \
1339
            --username "${ac_username}" \
1340
            --password "@keychain:${ac_keychain}" \
1341
            --file "${f}")"
1342

1343
        if [ 0 -ne $? ]; then
1344
            logError "Submission failed!"
1345
            exitError "Error message:\n${status}"
1346
        fi
1347

1348
        local ticket="$(echo "${status}" | grep -oE '[a-f0-9-]+$')"
1349
        logInfo "Submission successful. Ticket ID: ${ticket}."
1350

1351
        logInfo "Waiting for notarization to finish (this may take a while)..."
1352
        while true; do
1353
            echo -n "."
1354

1355
            status="$(xcrun altool --notarization-info "${ticket}" \
1356
                --username "${ac_username}" \
1357
                --password "@keychain:${ac_keychain}" 2> /dev/null)"
1358

1359
            if echo "$status" | grep -q "Status Code: 0"; then
1360
                logInfo "\nNotarization successful."
1361
                break
1362
            elif echo "$status" | grep -q "Status Code"; then
1363
                logError "\nNotarization failed!"
1364
                exitError "Error message:\n${status}"
1365
            fi
1366

1367
            sleep 5
1368
        done
1369

1370
        logInfo "Stapling ticket to disk image..."
1371
        xcrun stapler staple "${f}"
1372

1373
        if [ 0 -ne $? ]; then
1374
            exitError "Stapling failed!"
1375
        fi
1376

1377
        logInfo "Disk image successfully notarized."
1378
    done
1379
}
1380

1381

1382
# -----------------------------------------------------------------------
1383
#                              i18n command
1384
# -----------------------------------------------------------------------
1385

1386
i18n() {
1387
    local cmd="$1"
1388
    if [ -z "$cmd" ]; then
1389
        logError "No subcommand specified.\n"
1390
        printUsage i18n
1391
        exit 1
1392
    elif [ "$cmd" != "tx-push" ] && [ "$cmd" != "tx-pull" ] && [ "$cmd" != "lupdate" ]; then
1393
        logError "Unknown subcommand: '${cmd}'\n"
1394
        printUsage i18n
1395
        exit 1
1396
    fi
1397
    shift
1398

1399
    checkGitRepository
1400

1401
    if [ "$cmd" == "lupdate" ]; then
1402
        if [ ! -d share/translations ]; then
1403
            logError "Command must be called from repository root directory."
1404
            exit 1
1405
        fi
1406

1407
        checkQt5LUpdateExists
1408

1409
        logInfo "Updating source translation file..."
1410
        LUPDATE=lupdate-qt5
1411
        if ! command -v $LUPDATE > /dev/null; then
1412
            LUPDATE=lupdate
1413
        fi
1414
        $LUPDATE -no-ui-lines -disable-heuristic similartext -locations none -extensions c,cpp,h,js,mm,qrc,ui \
1415
            -no-obsolete src -ts share/translations/keepassxc_en.ts $@
1416

1417
        return 0
1418
    fi
1419

1420
    checkTransifexCommandExists
1421

1422
    local branch="$(git branch --show-current 2>&1)"
1423
    local real_branch="$branch"
1424
    if [[ "$branch" =~ ^release/ ]]; then
1425
        logInfo "Release branch, setting language resource to master branch."
1426
        branch="master"
1427
    elif [ "$branch" != "develop" ] && [ "$branch" != "master" ]; then
1428
        logError "Must be on master or develop branch!"
1429
        exit 1
1430
    fi
1431
    local resource="keepassxc.share-translations-keepassxc-en-ts--${branch}"
1432

1433
    if [ "$cmd" == "tx-push" ]; then
1434
        echo -e "This will push the \e[1m'en'\e[0m source file from the current branch to Transifex:\n" >&2
1435
        echo -e "   \e[1m${real_branch}\e[0m -> \e[1m${resource}\e[0m\n" >&2
1436
        echo -n "Continue? [y/N] " >&2
1437
        read -r yesno
1438
        if [ "$yesno" != "y" ] && [ "$yesno" != "Y" ]; then
1439
            logError "Push aborted."
1440
            exit 1
1441
        fi
1442

1443
        logInfo "Pushing source translation file to Transifex..."
1444
        tx push -s --use-git-timestamps -r "$resource" $@
1445

1446
    elif [ "$cmd" == "tx-pull" ]; then
1447
        logInfo "Pulling updated translations from Transifex..."
1448
        tx pull -af --minimum-perc=60 -r "$resource" $@
1449
    fi
1450
}
1451

1452

1453
# -----------------------------------------------------------------------
1454
#                       parse global command line
1455
# -----------------------------------------------------------------------
1456
MODE="$1"
1457
shift || true
1458
if [ -z "$MODE" ]; then
1459
    logError "Missing arguments!\n"
1460
    printUsage
1461
    exit 1
1462
elif [ "help" == "$MODE" ]; then
1463
    printUsage "$1"
1464
    exit
1465
elif [ "check" == "$MODE" ] || [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] \
1466
    || [ "gpgsign" == "$MODE" ] || [ "appsign" == "$MODE" ]|| [ "notarize" == "$MODE" ] \
1467
    || [ "appimage" == "$MODE" ]|| [ "i18n" == "$MODE" ]; then
1468
    ${MODE} "$@"
1469
else
1470
    printUsage "$MODE"
1471
fi
1472

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.