From 7faf530863ef68c604719d8b4f0ee58dad5c457a Mon Sep 17 00:00:00 2001 From: scito Date: Sat, 4 Feb 2023 22:20:57 +0100 Subject: [PATCH] reproducible builds, build.sh: linux/arm64 --- .github/workflows/ci_release.yml | 8 +++-- Pipfile.lock | 51 +++++++++++++++++++++----------- README.md | 6 ++-- build.sh | 23 ++++++++++++-- src/extract_otp_secrets.py | 6 ++-- 5 files changed, 67 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 690737a..f0268b8 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -56,6 +56,7 @@ jobs: echo "tag_message=$(git tag -l --format='%(contents:subject)' ${{ github.ref_name }})" >> $GITHUB_OUTPUT env: TAG_NAME: ${{ github.ref_name }} + PYTHONHASHSEED: 31 - name: Create Release id: create_release if: startsWith(github.ref, 'refs/tags/v') @@ -149,8 +150,8 @@ jobs: # https://hub.docker.com/r/multiarch/qemu-user-static/ - name: Run Pyinstaller in container for ${{ matrix.EXE }} run: | - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes --pull always - docker run --platform ${{ matrix.PLATFORM }} --pull always --entrypoint /bin/bash --rm -v "$(pwd)":/files -w /files scit0/extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U -r /files/requirements.txt && pip install pyinstaller && pyinstaller -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name ${{ matrix.EXE }} --distpath /files/dist/ /files/src/extract_otp_secrets.py' + docker run --pull always --rm --privileged multiarch/qemu-user-static --reset -p yes + docker run --platform ${{ matrix.PLATFORM }} --pull always --entrypoint /bin/bash --rm -v "$(pwd)":/files -w /files scit0/extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && 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 ${{ matrix.EXE }} --distpath /files/dist/ /files/src/extract_otp_secrets.py' - name: Smoke tests ${{ matrix.PLATFORM }} if: matrix.PLATFORM == 'linux/amd64' @@ -268,6 +269,9 @@ jobs: 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 - name: Build with pyinstaller for ${{ matrix.TARGET }} + env: + # Reproducible build: https://pyinstaller.org/en/stable/advanced-topics.html#creating-a-reproducible-build + PYTHONHASHSEED: 31 run: ${{ matrix.CMD_BUILD }} - name: Smoke tests for generated exe (general) run: | diff --git a/Pipfile.lock b/Pipfile.lock index d08d2d4..46ee2ac 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -194,6 +194,13 @@ "index": "pypi", "version": "==4.21.12" }, + "pypng": { + "hashes": [ + "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c", + "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1" + ], + "version": "==0.20220715.0" + }, "pyzbar": { "hashes": [ "sha256:13e3ee5a2f3a545204a285f41814d5c0db571967e8d4af8699a03afc55182a9c", @@ -205,10 +212,11 @@ }, "qrcode": { "hashes": [ - "sha256:375a6ff240ca9bd41adc070428b5dfc1dcfbb0f2507f1ac848f6cded38956578" + "sha256:26185f4c48ea8a896d5c9a0b080c41b75448e30b533b418ea0c65d91d10f0901", + "sha256:ba7520e031f28dadcd92049a88832a585111d09342bd0d15ddae5ccf5af98c12" ], "index": "pypi", - "version": "==7.3.1" + "version": "==7.4.1" }, "qreader": { "hashes": [ @@ -216,16 +224,24 @@ ], "index": "pypi", "version": "==1.3.2" + }, + "typing-extensions": { + "hashes": [ + "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", + "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" + ], + "markers": "python_version >= '3.7'", + "version": "==4.4.0" } }, "develop": { "astroid": { "hashes": [ - "sha256:14c1603c41cc61aae731cad1884a073c4645e26f126d13ac8346113c95577f3b", - "sha256:6afc22718a48a689ca24a97981ad377ba7fb78c133f40335dfd16772f29bcfb1" + "sha256:23c718921acab5f08cbbbe9293967f1f8fec40c336d19cd75dc12a9ea31d2eb2", + "sha256:bd1aa4f9915c98e8aaebcd4e71930154d4e8c9aaf05d35ac0a63d1956091ae3f" ], "markers": "python_full_version >= '3.7.2'", - "version": "==2.13.3" + "version": "==2.14.1" }, "attrs": { "hashes": [ @@ -431,10 +447,11 @@ }, "mypy-extensions": { "hashes": [ - "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", - "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" ], - "version": "==0.4.3" + "markers": "python_version >= '3.5'", + "version": "==1.0.0" }, "mypy-protobuf": { "hashes": [ @@ -506,11 +523,11 @@ }, "pylint": { "hashes": [ - "sha256:9df0d07e8948a1c3ffa3b6e2d7e6e63d9fb457c5da5b961ed63106594780cc7e", - "sha256:b3dc5ef7d33858f297ac0d06cc73862f01e4f2e74025ec3eff347ce0bc60baf5" + "sha256:bad9d7c36037f6043a1e848a43004dfd5ea5ceb05815d713ba56ca4503a9fe37", + "sha256:ffe7fa536bb38ba35006a7c8a6d2efbfdd3d95bbf21199cad31f76b1c50aaf30" ], "index": "pypi", - "version": "==2.15.10" + "version": "==2.16.1" }, "pyproject-hooks": { "hashes": [ @@ -546,11 +563,11 @@ }, "setuptools": { "hashes": [ - "sha256:883131c5b6efa70b9101c7ef30b2b7b780a4283d5fc1616383cdf22c83cbefe6", - "sha256:9d790961ba6219e9ff7d9557622d2fe136816a264dd01d5997cfc057d804853d" + "sha256:a7687c12b444eaac951ea87a9627c4f904ac757e7abdc5aac32833234af90378", + "sha256:e261cdf010c11a41cb5cb5f1bf3338a7433832029f559a6a7614bd42a967c300" ], "markers": "python_version >= '3.7'", - "version": "==67.0.0" + "version": "==67.1.0" }, "setuptools-git-versioning": { "hashes": [ @@ -570,11 +587,11 @@ }, "types-protobuf": { "hashes": [ - "sha256:6c87c7f8df61d57a53de8221777e4fcc3c7ed24419fbf43b8e9f50887f3773fa", - "sha256:824109e0fe87525a9d2da4cc4eec36ca004f1a0f3d1c0838cfd2873a484cffdd" + "sha256:09d39f2c84d0c9c323f44a4c6f1f88fbb1aac0c855f89a3c2746b50c823360cc", + "sha256:7e1f8641b013f1500ee3b56ab4596df26b325d61bc0c69c6eb58ec65f4e41ad8" ], "index": "pypi", - "version": "==4.21.0.3" + "version": "==4.21.0.4" }, "typing-extensions": { "hashes": [ diff --git a/README.md b/README.md index 81cb8c3..4a76fd0 100644 --- a/README.md +++ b/README.md @@ -95,16 +95,16 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or :heavy_check_mark: No installation needed, neither Python nor any dependencies have to be installed. :heavy_check_mark: Easy and convenient -> :information_source: There is a delay after starting the executable since the files have internally to be unpacked. +:information_source: There is a delay after starting the executable since the files have internally to be unpacked. -> :information_source: If you are a developer, you might prefer to run the Python script directly, see [Installation](#installation-of-python-script-recommend-for-developers-or-advanced-users) +:information_source: If you are a developer, you might prefer to run the Python script directly, see [Installation](#installation-of-python-script-recommend-for-developers-or-advanced-users) > :warning: Some antivirus tools may show a virus or trojan alert for the executable. > This alert is a false positive. > This is a known problem for executables generated by PyInstaller. > If you have any doubt, please use directly the [Python script](#installation-of-python-script-recommend-for-developers-or-advanced-users). -> :information_source: The executables are not signed. Thus, the operating system may show a warning about download from unknown source. +:information_source: The executables are not signed. Thus, the operating system may show a warning about download from unknown source. ## Usage diff --git a/build.sh b/build.sh index cf473ee..323e4bf 100755 --- a/build.sh +++ b/build.sh @@ -80,6 +80,7 @@ clean_flag="" build_docker=true run_gui=true generate_result_files=false +PYTHONHASHSEED=31 while test $# -gt 0; do case $1 in @@ -436,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 -r /files/requirements.txt && pip install pyinstaller && 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 --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" @@ -448,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 -r /files/requirements.txt && pip install pyinstaller && 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 --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" @@ -461,6 +462,24 @@ if $build_docker; then if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi eval "$cmd" + # build linux/arm64 + # Build extract_otp_secrets (Debian Buster) + cmd="docker buildx build --platform=linux/arm64 . -t extract_otp_secrets:buster --pull --build-arg RUN_TESTS=false --build-arg BASE_IMAGE=python:3.11-slim-buster" + if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi + eval "$cmd" + + cmd="docker run --pull always --rm --privileged multiarch/qemu-user-static --reset -p yes" + 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'" + if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi + eval "$cmd" + + cmd="PLATFORM='linux/arm64' && EXE='dist/extract_otp_secrets_linux_arm64' && docker run --platform \"\$PLATFORM\" --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c \"\$EXE -V && \$EXE -h && \$EXE example_export.png && \$EXE - < example_export.txt && \$EXE --qr ZBAR example_export.png && \$EXE --qr QREADER example_export.png && \$EXE --qr QREADER_DEEP example_export.png && \$EXE --qr CV2 example_export.png && \$EXE --qr CV2_WECHAT example_export.png\"" + if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi + eval "$cmd" + # Run GUI from Docker if $run_gui; then cmd="docker run --rm -v "$(pwd)":/files:ro --device=\"/dev/video0:/dev/video0\" --env=\"DISPLAY\" -v /tmp/.X11-unix:/tmp/.X11-unix:ro extract_otp_secrets &" diff --git a/src/extract_otp_secrets.py b/src/extract_otp_secrets.py index 0fa7bc8..3a74d06 100644 --- a/src/extract_otp_secrets.py +++ b/src/extract_otp_secrets.py @@ -786,8 +786,8 @@ def check_file_exists(filename: str) -> None: def has_no_otps_show_warning(otps: Otps) -> bool: if len(otps) == 0: - tkinter.messagebox.showinfo(title="No data", message="There are no otp secrets to write") - tk_root.update() # dispose dialog + tkinter.messagebox.showinfo(title="No data", message="There are no otp secrets to write") + tk_root.update() # dispose dialog return len(otps) == 0 @@ -815,7 +815,7 @@ def do_debug_checks() -> bool: def is_not_headless() -> bool: if headless: - log_warn(f"Cannot open dialog in headless mode") + log_warn("Cannot open dialog in headless mode") return not headless