From eb8ea3330ffa894caf1e0ab8c55b92a98108288d Mon Sep 17 00:00:00 2001 From: scito Date: Tue, 7 Feb 2023 17:16:35 +0100 Subject: [PATCH] release: improve macos docs after macos tests - build macos dmg - however, do not upload macos dmg to assets as it cannot be executed since the app and installer are not signed and notarized - upload artifacts - tested executable on macOS --- .github/workflows/ci_release.yml | 78 +++++++----- .gitignore | 5 +- Pipfile.lock | 18 +-- README.md | 26 +++- build.sh | 17 ++- docs/README_TOC.md | 3 +- installer/build_dmg.sh | 22 ++++ .../extract_otp_secrets_macos_template.spec | 111 ++++++++++++++++++ .../win_file_version_info_template.txt | 0 9 files changed, 232 insertions(+), 48 deletions(-) create mode 100755 installer/build_dmg.sh create mode 100644 installer/extract_otp_secrets_macos_template.spec rename file_version_info_template.txt => installer/win_file_version_info_template.txt (100%) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 11ae774..2acdc45 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -20,6 +20,10 @@ name: release # https://peps.python.org/pep-0440/ # https://semver.org/ +# macOS: +# https://pyinstaller.org/en/stable/usage.html#building-macos-app-bundles +# https://github.com/pyinstaller/pyinstaller/wiki/Recipe-OSX-Code-Signing + # Build matrix: # - Linux x86_64 glibc 2.35: ubuntu-latest # - Linux x86_64 glibc 2.34: extract_otp_secrets:buster @@ -83,7 +87,7 @@ jobs: https://api.github.com/repos/scito/extract_otp_secrets/releases \ --silent \ --show-error \ - -d '{"tag_name":"${{ github.ref }}","target_commitish":"master","name":"${{ steps.meta.outputs.version }} - ${{ steps.meta.outputs.date }}","body":"${{ steps.meta.outputs.tag_message }}\n\nDownload the executable for your platform and execute it.\n\n | Executable | Description |\n | --- | --- |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_linux_x86_64 | Linux x86_64/amd64 (glibc >= 2.28) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_linux_arm64 | Linux arm64 (glibc >= 2.28) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_win_x86_64.exe | Windows x86_64/amd64/x64 |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_win_arm64.exe | N/A |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_x86_64 | MacOS x86_64/amd64 (barely tested due to lack of Apple environment; maybe execute permission must be set manually to the executable; optional libzbar must be installed separately, see README.md) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_arm64 | N/A |\n ","draft":true,"prerelease":false,"generate_release_notes":true}') + -d '{"tag_name":"${{ github.ref }}","target_commitish":"master","name":"${{ steps.meta.outputs.version }} - ${{ steps.meta.outputs.date }}","body":"${{ steps.meta.outputs.tag_message }}\n\nDownload the executable for your platform and execute it.\n\n | Executable | Description |\n | --- | --- |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_linux_x86_64 | Linux x86_64/amd64 (glibc >= 2.28) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_linux_arm64 | Linux arm64 (glibc >= 2.28) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_win_x86_64.exe | Windows x86_64/amd64/x64 |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_win_arm64.exe | N/A |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_x86_64.dmg | N/A, see [README.md](https://github.com/scito/extract_otp_secrets#readme) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_x86_64.pkg | N/A, see [README.md](https://github.com/scito/extract_otp_secrets#readme) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_x86_64 | MacOS x86_64/amd64 (bare executable, see [README.md](https://github.com/scito/extract_otp_secrets#readme); optional libzbar must be installed manually, see [README.md](https://github.com/scito/extract_otp_secrets#readme)) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_arm64 | N/A |\n","draft":true,"prerelease":false,"generate_release_notes":true}') echo upload_url=$(jq '.upload_url' <<< "$response") >> $GITHUB_OUTPUT echo $(jq -r '.upload_url' <<< "$response") > release_url.txt echo $(jq -r '.id' <<< "$response") > release_id.txt @@ -193,6 +197,11 @@ jobs: name: release_url - name: Display structure of files run: ls -R + - name: Upload EXE to artifacts + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.EXE }} + path: dist/${{ matrix.EXE }} - name: Upload Release Asset id: upload-release-asset if: startsWith(github.ref, 'refs/tags/v') @@ -222,34 +231,33 @@ jobs: # TODO add --manifest # TODO find more elegant solution for pyzbar\libiconv.dll and pyzbar\libzbar-64.dll # Files of Visual C++ 2013 Redistributable Package: https://support.microsoft.com/en-us/topic/update-for-visual-c-2013-redistributable-package-d8ccd6a5-4e26-c290-517b-8da6cfdf4f10 - OUT_FILE_NAME: extract_otp_secrets.exe + EXE: extract_otp_secrets.exe ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_win_x86_64.exe ASSET_MIME: application/vnd.microsoft.portable-executable UPLOAD: true CMD_BUILD: | - pyinstaller -y --add-data "$($Env:pythonLocation)\__yolo_v3_qr_detector;__yolo_v3_qr_detector" --add-binary "$($Env:pythonLocation)\Lib\site-packages\pyzbar\libiconv.dll;pyzbar" --add-binary "$($Env:pythonLocation)\Lib\site-packages\pyzbar\libzbar-64.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\msvcr120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\msvcp120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\vcamp120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\vcomp120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\vccorlib120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120u.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120chs.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120cht.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120deu.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120enu.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120esn.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120fra.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120ita.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120jpn.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120kor.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120rus.dll;pyzbar" --onefile --version-file build\file_version_info.txt src\extract_otp_secrets.py + pyinstaller -y --add-data "$($Env:pythonLocation)\__yolo_v3_qr_detector;__yolo_v3_qr_detector" --add-binary "$($Env:pythonLocation)\Lib\site-packages\pyzbar\libiconv.dll;pyzbar" --add-binary "$($Env:pythonLocation)\Lib\site-packages\pyzbar\libzbar-64.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\msvcr120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\msvcp120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\vcamp120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\vcomp120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\vccorlib120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120u.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120chs.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120cht.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120deu.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120enu.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120esn.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120fra.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120ita.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120jpn.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120kor.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120rus.dll;pyzbar" --onefile --version-file build\win_file_version_info.txt --name extract_otp_secrets.exe src\extract_otp_secrets.py - os: macos-11 TARGET: macos - # TODO add --icon - # TODO add --osx-bundle-identifier - # TODO add --codesign-identity - # TODO add --osx-entitlements-file - # TODO https://pyinstaller.org/en/stable/spec-files.html#spec-file-options-for-a-macos-bundle - # TODO --target-arch universal2 - OUT_FILE_NAME: extract_otp_secrets + # https://pyinstaller.org/en/stable/spec-files.html#spec-file-options-for-a-macos-bundle + EXE: extract_otp_secrets ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_macos_x86_64 - ASSET_MIME: application/x-newton-compatible-pkg + DMG: extract_otp_secrets.dmg + ASSET_NAME_DMG: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_macos_x86_64.dmg + ASSET_MIME: application/octet-stream UPLOAD: true CMD_BUILD: | - pyinstaller -y --add-data $macos_python_path/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --argv-emulation src/extract_otp_secrets.py + VERSION_STR=$(setuptools-git-versioning) COPYRIGHT_YEARS='2020-2023' envsubst < installer/extract_otp_secrets_macos_template.spec > extract_otp_secrets_macos.spec + pyinstaller -y extract_otp_secrets_macos.spec + installer/build_dmg.sh - os: ubuntu-latest TARGET: linux - OUT_FILE_NAME: extract_otp_secrets + EXE: extract_otp_secrets_ubuntu ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_linux_x86_64_ubuntu_latest ASSET_MIME: application/x-executable UPLOAD: false CMD_BUILD: | - pyinstaller -y --add-data $pythonLocation/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile src/extract_otp_secrets.py + pyinstaller -y --add-data $pythonLocation/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_ubuntu src/extract_otp_secrets.py steps: - name: Output path if: runner.os == 'Windows' @@ -273,18 +281,18 @@ jobs: - name: Install zbar shared lib for QReader (macOS) if: runner.os == 'macOS' run: | - brew install zbar + brew install zbar create-dmg - name: Install dependencies # TODO fix --use-pep517 run: | python -m pip install --upgrade pip pip install -U -r requirements-dev.txt pip install -U . - - name: Create Windows file_version_info.txt + - name: Create Windows win_file_version_info.txt shell: bash run: | mkdir -p build/ - VERSION_STR=$(setuptools-git-versioning) VERSION_MAJOR=$(cut -d '.' -f 1 <<< "$(setuptools-git-versioning)") VERSION_MINOR=$(cut -d '.' -f 2 <<< "$(setuptools-git-versioning)") VERSION_PATCH=$(echo $(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") | sed -E -n "s/^([0-9]+).*/\1/p") VERSION_BUILD=$(echo $(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") | sed -E -n -e"s/^[0-9]+.+/99/p")$(($(git rev-list --count $(git tag | sort -V -r | sed '1!d')..HEAD))) YEARS='2020-2023' envsubst < file_version_info_template.txt > build/file_version_info.txt + VERSION_STR=$(setuptools-git-versioning) VERSION_MAJOR=$(cut -d '.' -f 1 <<< "$(setuptools-git-versioning)") VERSION_MINOR=$(cut -d '.' -f 2 <<< "$(setuptools-git-versioning)") VERSION_PATCH=$(echo $(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") | sed -E -n "s/^([0-9]+).*/\1/p") VERSION_BUILD=$(echo $(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") | sed -E -n -e"s/^[0-9]+.+/99/p")$(($(git rev-list --count $(git tag | sort -V -r | sed '1!d')..HEAD))) COPYRIGHT_YEARS='2020-2023' envsubst < installer/win_file_version_info_template.txt > build/win_file_version_info.txt - name: Build with pyinstaller for ${{ matrix.TARGET }} env: # Reproducible build: https://pyinstaller.org/en/stable/advanced-topics.html#creating-a-reproducible-build @@ -292,18 +300,18 @@ jobs: run: ${{ matrix.CMD_BUILD }} - name: Smoke tests for generated exe (general) run: | - dist/${{ matrix.OUT_FILE_NAME }} -V - dist/${{ matrix.OUT_FILE_NAME }} -h - dist/${{ matrix.OUT_FILE_NAME }} example_export.png - dist/${{ matrix.OUT_FILE_NAME }} --qr ZBAR example_export.png - dist/${{ matrix.OUT_FILE_NAME }} --qr QREADER example_export.png - dist/${{ matrix.OUT_FILE_NAME }} --qr QREADER_DEEP example_export.png - dist/${{ matrix.OUT_FILE_NAME }} --qr CV2 example_export.png - dist/${{ matrix.OUT_FILE_NAME }} --qr CV2_WECHAT example_export.png + dist/${{ matrix.EXE }} -V + dist/${{ matrix.EXE }} -h + dist/${{ matrix.EXE }} example_export.png + dist/${{ matrix.EXE }} --qr ZBAR example_export.png + dist/${{ matrix.EXE }} --qr QREADER example_export.png + dist/${{ matrix.EXE }} --qr QREADER_DEEP example_export.png + dist/${{ matrix.EXE }} --qr CV2 example_export.png + dist/${{ matrix.EXE }} --qr CV2_WECHAT example_export.png - name: Smoke tests for generated exe (stdin) if: runner.os != 'Windows' run: | - dist/${{ matrix.OUT_FILE_NAME }} - < example_export.txt + dist/${{ matrix.EXE }} - < example_export.txt - name: Load Release URL File from release job if: startsWith(github.ref, 'refs/tags/v') uses: actions/download-artifact@v3 @@ -323,11 +331,27 @@ jobs: run: | echo "release_id=$(cat release_id.txt)" >> $GITHUB_OUTPUT echo "upload_url=https://uploads.github.com/repos/scito/extract_otp_secrets/releases/$(cat release_id.txt)/assets?name=" >> $GITHUB_OUTPUT + - name: Upload EXE to artifacts + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.EXE }} + path: dist/${{ matrix.EXE }} + - name: Upload DMG to artifacts + uses: actions/upload-artifact@v3 + if: runner.os == 'macOS' + with: + name: ${{ matrix.DMG }} + path: dist/${{ matrix.DMG }} - name: Upload Release Asset id: upload-release-asset if: matrix.UPLOAD && startsWith(github.ref, 'refs/tags/v') run: | - curl -X POST -H "Accept: application/vnd.github+json" -H "Content-Type: ${{ matrix.ASSET_MIME }}" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --show-error --data-binary @dist/${{ matrix.OUT_FILE_NAME }} ${{ steps.meta.outputs.upload_url }}=${{ matrix.ASSET_NAME }} + curl -X POST -H "Accept: application/vnd.github+json" -H "Content-Type: ${{ matrix.ASSET_MIME }}" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --show-error --data-binary @dist/${{ matrix.EXE }} ${{ steps.meta.outputs.upload_url }}=${{ matrix.ASSET_NAME }} + - name: Upload Release Asset DMG (macOS) + id: upload-release-asset-dmg + if: false && matrix.UPLOAD && startsWith(github.ref, 'refs/tags/v') && runner.os == 'macOS' + run: | + curl -X POST -H "Accept: application/vnd.github+json" -H "Content-Type: ${{ matrix.ASSET_MIME }}" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --show-error --data-binary @dist/${{ matrix.DMG }} ${{ steps.meta.outputs.upload_url }}=${{ matrix.ASSET_NAME_DMG }} upload-hashes: name: Upload hashes diff --git a/.gitignore b/.gitignore index 5be2d96..e93450b 100644 --- a/.gitignore +++ b/.gitignore @@ -20,9 +20,12 @@ dist/ pytest-coverage.txt tests/reports/ dist_*/ -*.spec file_version_info_python.txt file_version_info_explorer.txt file_version_info.txt assets/* +extract_otp_secrets_linux_arm64.spec +extract_otp_secrets_linux_x86_64_bullseye.spec +extract_otp_secrets_linux_x86_64.spec +extract_otp_secrets.spec diff --git a/Pipfile.lock b/Pipfile.lock index 5c5673b..1f50e67 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -467,11 +467,11 @@ }, "platformdirs": { "hashes": [ - "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490", - "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2" + "sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9", + "sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567" ], "markers": "python_version >= '3.7'", - "version": "==2.6.2" + "version": "==3.0.0" }, "pluggy": { "hashes": [ @@ -559,11 +559,11 @@ }, "setuptools": { "hashes": [ - "sha256:a7687c12b444eaac951ea87a9627c4f904ac757e7abdc5aac32833234af90378", - "sha256:e261cdf010c11a41cb5cb5f1bf3338a7433832029f559a6a7614bd42a967c300" + "sha256:16ccf598aab3b506593c17378473978908a2734d7336755a8769b480906bec1c", + "sha256:b440ee5f7e607bb8c9de15259dba2583dd41a38879a7abc1d43a71c59524da48" ], "markers": "python_version >= '3.7'", - "version": "==67.1.0" + "version": "==67.2.0" }, "setuptools-git-versioning": { "hashes": [ @@ -583,11 +583,11 @@ }, "types-protobuf": { "hashes": [ - "sha256:09d39f2c84d0c9c323f44a4c6f1f88fbb1aac0c855f89a3c2746b50c823360cc", - "sha256:7e1f8641b013f1500ee3b56ab4596df26b325d61bc0c69c6eb58ec65f4e41ad8" + "sha256:46ffa6647e2f8d53a4828e905f8fb0e8ff8c918309b425572dd34ab4d0b48553", + "sha256:819a7c67e69476e39c3f0c9871bbb9ee82313645d317b6daeb60ac95a309dbd3" ], "index": "pypi", - "version": "==4.21.0.4" + "version": "==4.21.0.5" }, "typing-extensions": { "hashes": [ diff --git a/README.md b/README.md index f21278f..d2b1c3a 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or ## Table of contents -- [Download binary executable (πŸ†• since v2.1)](#download-binary-executable--since-v21) +- [Download and run binary executable (πŸ†• since v2.1)](#download-and-run-binary-executable--since-v21) + - [MacOS application](#macos-application) - [Usage](#usage) - [Capture QR codes from camera (πŸ†• since version 2.0)](#capture-qr-codes-from-camera--since-version-20) - [With builtin QR decoder from image files (πŸ†• since version 2.0)](#with-builtin-qr-decoder-from-image-files--since-version-20) @@ -86,10 +87,11 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or - [Related projects](#related-projects) -## Download binary executable (πŸ†• since v2.1) +## Download and run binary executable (πŸ†• since v2.1) 1. Download executable for your platform from [latest release](https://github.com/scito/extract_otp_secrets/releases/latest), see assets -2. Start executable by clicking or from command line +2. Linux and macOS: Set executable bit for the downloaded file, e.g in terminal with `chmod +x extract_otp_secrets_OS_vX.X.X` +3. Start executable by clicking or from command line (macOS: startable only from command line, see [below](#macOS-application)) :heavy_check_mark: Everything is just packed in one executable. :heavy_check_mark: No installation needed, neither Python nor any dependencies have to be installed. @@ -106,6 +108,20 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or :information_source: The executables are not signed. Thus, the operating system may show a warning about download from unknown source. +### MacOS application + +> Beginning in macOS 10.15, all software built after June 1, 2019, and distributed with Developer ID must be notarized. However, you aren’t required to notarize software that you distribute through the Mac App Store because the App Store submission process already includes equivalent security checks. [developer.apple.com](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution) + +:x: Unfortunately, I cannot provide a signed and notarized installable application for macOS as .dmg or .pkg. Apple is not Open Source friendly and requires a yearly Developer ID subscription. I am not willing to pay [USD 99 per year](https://developer.apple.com/support/compare-memberships/) to Apple for this little open source tool. + +However, the bare executable can be executed from the command line. + +1. Download executable for macOS platform from [latest release](https://github.com/scito/extract_otp_secrets/releases/latest), see assets +2. Open Terminal application +3. Change to Downloads: `cd $HOME/Downloads` +4. Set executable bit for the downloaded file: `chmod +x extract_otp_secrets_OS_vX.X.X` +5. Start executable from command line: `./extract_otp_secrets_OS_vX.X.X` + ## Usage ### Capture QR codes from camera (πŸ†• since version 2.0) @@ -327,7 +343,9 @@ python extract_otp_secrets.py = < example_export.png * extract_otp_secrets_linux_x86_64 (requires glibc >= 2.28) * extract_otp_secrets_linux_arm64 (requires glibc >= 2.28) * extract_otp_secrets_win_x86_64.exe - * extract_otp_secrets_macos_x86_64 (barely tested, [libzbar](#installation-of-optional-shared-system-libraries-recommended) needs to be installed additionally if needed) + * extract_otp_secrets_macos_x86_64 (optional [libzbar](#installation-of-optional-shared-system-libraries-recommended) needs to be installed manually if needed) + * extract_otp_secrets_macos_x86_64.dmg N/A, see [why](#macos-application) + * extract_otp_secrets_macos_x86_64.pkg N/A, see [why](#macos-application) * Prebuilt Docker images provided for amd64 and arm64 (πŸ†• since v2.0) * Many ways to run the script: * Native Python diff --git a/build.sh b/build.sh index 323e4bf..8c91ca8 100755 --- a/build.sh +++ b/build.sh @@ -349,7 +349,7 @@ if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}"; eval "$cmd" echo "local glibc: $LOCAL_GLIBC_VERSION" -cmd="pyinstaller -y --add-data $HOME/.local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile $clean_flag src/extract_otp_secrets.py" +cmd="pyinstaller -y --specpath installer --add-data $HOME/.local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile $clean_flag src/extract_otp_secrets.py" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi eval "$cmd" @@ -437,7 +437,7 @@ if $build_docker; then BULLSEYE_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"') echo "Bullseye glibc: $BULLSEYE_GLIBC_VERSION" - cmd="docker run --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64_bullseye --distpath /files/dist/ /files/src/extract_otp_secrets.py'" + cmd="docker run --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --specpath installer --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64_bullseye --distpath /files/dist/ /files/src/extract_otp_secrets.py'" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi eval "$cmd" @@ -449,7 +449,7 @@ if $build_docker; then BUSTER_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets:buster -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"') echo "Bullseye glibc: $BUSTER_GLIBC_VERSION" - cmd="docker run --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64 --distpath /files/dist/ /files/src/extract_otp_secrets.py'" + cmd="docker run --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --specpath installer --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64 --distpath /files/dist/ /files/src/extract_otp_secrets.py'" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi eval "$cmd" @@ -457,8 +457,13 @@ if $build_docker; then if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi eval "$cmd" - # create Windows file_version_info.txt - cmd="VERSION_STR=$(setuptools-git-versioning) VERSION_MAJOR=$(cut -d '.' -f 1 <<< "$(setuptools-git-versioning)") VERSION_MINOR=$(cut -d '.' -f 2 <<< "$(setuptools-git-versioning)") VERSION_PATCH=$(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") VERSION_BUILD=$(($(git rev-list --count $(git tag | sort -V -r | sed '1!d')..HEAD))) YEARS='2020-2023' envsubst < file_version_info_template.txt > build/file_version_info.txt" + # create Windows win_file_version_info.txt + cmd="VERSION_STR=$(setuptools-git-versioning) VERSION_MAJOR=$(cut -d '.' -f 1 <<< "$(setuptools-git-versioning)") VERSION_MINOR=$(cut -d '.' -f 2 <<< "$(setuptools-git-versioning)") VERSION_PATCH=$(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") VERSION_BUILD=$(($(git rev-list --count $(git tag | sort -V -r | sed '1!d')..HEAD))) COPYRIGHT_YEARS='2020-2023' envsubst < installer/win_file_version_info_template.txt > build/win_file_version_info.txt" + if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi + eval "$cmd" + + # create macOS extract_otp_secrets_macos.spec from extract_otp_secrets_macos_template.spec + cmd="VERSION_STR=$(setuptools-git-versioning) COPYRIGHT_YEARS='2020-2023' envsubst < installer/extract_otp_secrets_macos_template.spec > build/extract_otp_secrets_macos.spec" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi eval "$cmd" @@ -472,7 +477,7 @@ if $build_docker; then if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi eval "$cmd" - cmd="docker run --platform linux/arm64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_arm64 --distpath /files/dist/ /files/src/extract_otp_secrets.py'" + cmd="docker run --platform linux/arm64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller --specpath installer -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_arm64 --distpath /files/dist/ /files/src/extract_otp_secrets.py'" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi eval "$cmd" diff --git a/docs/README_TOC.md b/docs/README_TOC.md index 892dac1..6e9c008 100644 --- a/docs/README_TOC.md +++ b/docs/README_TOC.md @@ -3,7 +3,8 @@ Generate from file: README.md ## Table of contents - [Table of contents](#table-of-contents) -- [Download binary executable (πŸ†• since v2.1)](#download-binary-executable--since-v21) +- [Download and run binary executable (πŸ†• since v2.1)](#download-and-run-binary-executable--since-v21) + - [MacOS application](#macos-application) - [Usage](#usage) - [Capture QR codes from camera (πŸ†• since version 2.0)](#capture-qr-codes-from-camera--since-version-20) - [With builtin QR decoder from image files (πŸ†• since version 2.0)](#with-builtin-qr-decoder-from-image-files--since-version-20) diff --git a/installer/build_dmg.sh b/installer/build_dmg.sh new file mode 100755 index 0000000..87c3139 --- /dev/null +++ b/installer/build_dmg.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# Create a folder (named dmg) to prepare our DMG in (if it doesn't already exist). + +# https://www.pythonguis.com/tutorials/packaging-pyqt5-applications-pyinstaller-macos-dmg/ + +mkdir -p dist/dmg +# Empty the dmg folder. +rm -r dist/dmg/* +# Copy the app bundle to the dmg folder. +cp -r "dist/extract_otp_secrets.app" dist/dmg +# If the DMG already exists, delete it. +test -f "dist/extract_otp_secrets.dmg" && rm "dist/extract_otp_secrets.dmg" +create-dmg \ + --volname "Extract OTP Secrets" \ + --window-pos 200 120 \ + --window-size 600 300 \ + --icon-size 100 \ + --hide-extension "extract_otp_secrets.app" \ + --app-drop-link 425 120 \ + "dist/extract_otp_secrets.dmg" \ + "dist/dmg/" diff --git a/installer/extract_otp_secrets_macos_template.spec b/installer/extract_otp_secrets_macos_template.spec new file mode 100644 index 0000000..a47fe0b --- /dev/null +++ b/installer/extract_otp_secrets_macos_template.spec @@ -0,0 +1,111 @@ +# -*- mode: python ; coding: utf-8 -*- + +# https://www.pythonguis.com/tutorials/packaging-pyqt5-applications-pyinstaller-macos-dmg/ +# https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html + +block_cipher = None + +a = Analysis( + ['src/extract_otp_secrets.py'], + pathex=[], + binaries=[], + datas=[('$macos_python_path/__yolo_v3_qr_detector/', '__yolo_v3_qr_detector/')], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='extract_otp_secrets', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=True, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) +app = BUNDLE( + exe, + name='extract_otp_secrets.app', + icon=None, + bundle_identifier='ch.scito.tools.extract_otp_secrets', + version='$VERSION_STR', + info_plist={ + 'NSPrincipalClass': 'NSApplication', + 'NSAppleScriptEnabled': False, + 'NSHumanReadableCopyright': 'Copyright Β© $COPYRIGHT_YEARS Scito.', + 'CFBundleDocumentTypes': [ + # Reference: https://chromium.googlesource.com/chromium/src/+/lkgr/chrome/app/app-Info.plist + # https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/understanding_utis/understand_utis_conc/understand_utis_conc.html#//apple_ref/doc/uid/TP40001319-CH202-SW6 + # https://developer.apple.com/documentation/uniformtypeidentifiers/system-declared_uniform_type_identifiers + { + 'CFBundleTypeName': 'GIF image', + 'CFBundleTypeIconFile': 'document.icns', + 'LSItemContentTypes': ['com.compuserve.gif'], + 'LSHandlerRank': 'Alternate', + 'NSExportableTypes': ['public.json','public.comma-separated-values-text','public.plain-text'], + }, + { + 'CFBundleTypeName': 'JPEG image', + 'CFBundleTypeIconFile': 'document.icns', + 'LSItemContentTypes': ['public.jpeg'], + 'LSHandlerRank': 'Alternate', + 'NSExportableTypes': ['public.json','public.comma-separated-values-text','public.plain-text'], + }, + { + 'CFBundleTypeName': 'PNG image', + 'CFBundleTypeIconFile': 'document.icns', + 'LSItemContentTypes': ['public.png'], + 'LSHandlerRank': 'Alternate', + 'NSExportableTypes': ['public.json','public.comma-separated-values-text','public.plain-text'], + }, + { + 'CFBundleTypeName': 'WebP image', + 'CFBundleTypeIconFile': 'document.icns', + 'LSItemContentTypes': ['org.webmproject.webp'], + 'LSHandlerRank': 'Alternate', + 'NSExportableTypes': ['public.json','public.comma-separated-values-text','public.plain-text'], + }, + { + 'CFBundleTypeName': 'Tiff image', + 'CFBundleTypeIconFile': 'document.icns', + 'LSItemContentTypes': ['public.tiff'], + 'LSHandlerRank': 'Alternate', + 'NSExportableTypes': ['public.json','public.comma-separated-values-text','public.plain-text'], + }, + { + 'CFBundleTypeName': 'Bmp image', + 'CFBundleTypeIconFile': 'document.icns', + 'LSItemContentTypes': ['com.microsoft.bmp'], + 'LSHandlerRank': 'Alternate', + 'NSExportableTypes': ['public.json','public.comma-separated-values-text','public.plain-text'], + }, + { + 'CFBundleTypeName': 'Plain text document', + 'CFBundleTypeIconFile': 'document.icns', + 'LSItemContentTypes': ['public.plain-text'], + 'LSHandlerRank': 'Alternate', + 'NSExportableTypes': ['public.json','public.comma-separated-values-text','public.plain-text'], + }, + ], + }, +) diff --git a/file_version_info_template.txt b/installer/win_file_version_info_template.txt similarity index 100% rename from file_version_info_template.txt rename to installer/win_file_version_info_template.txt