Compare commits

..

12 Commits

Author SHA1 Message Date
scito
861d7d0da8 build buster image in ci_docker for speeding up releases 2023-01-30 11:28:07 +01:00
scito
63fc21cc90 ci_release: upload hashes and run on pull_request and run scheduled 2023-01-30 11:28:07 +01:00
scito
d234cf4112 cv2: fix save as csv/json/keepass 2023-01-30 11:28:07 +01:00
scito
970dbd3759 cv2: save as csv/json/keppass by key command 2023-01-29 16:50:55 +01:00
scito
197347a3e9 update README 2023-01-27 20:58:33 +01:00
scito
365d5ac432 use hub.docker for linux exe build
since it is more stable and thus faster
2023-01-26 01:14:45 +01:00
scito
88ff584e47 ci: workaround for failing builds 2023-01-26 00:18:41 +01:00
scito
dbb5d8f755 workaround ghcr.io failed to copy
failed to copy: io: read/write on closed pipe

- use DockerHub instead of dhcr.io
- ref: https://github.com/containerd/containerd/issues/7972
2023-01-25 23:46:22 +01:00
scito
580f94256f Add files of Visual C++ 2013 Redistributable Package
- add files of vcredist_x64.exe as binary data to pyzbar
- thus avoid manual installation of vcredist_x64.exe
- test all qr readers in exe smoketests
- improve README
2023-01-25 23:14:38 +01:00
scito
f4bff86a5c fix curl upload windows
- set for pre-releases 99 in windows version
- fix TZ: use TZ=Europe/Zurich
2023-01-25 01:54:20 +01:00
scito
cff5fe1cda ci_release: ignore response and remove silent mode 2023-01-24 23:18:28 +01:00
scito
576b1e68c5 ci: ignore markdown files 2023-01-24 23:00:32 +01:00
11 changed files with 417 additions and 143 deletions

View File

@@ -9,6 +9,7 @@ on:
push: push:
paths-ignore: paths-ignore:
- 'docs/**' - 'docs/**'
- '**.md'
# pull_request: # pull_request:
schedule: schedule:
# Run daily on default branch # Run daily on default branch

View File

@@ -13,6 +13,7 @@ on:
push: push:
paths-ignore: paths-ignore:
- 'docs/**' - 'docs/**'
- '**.md'
tags-ignore: tags-ignore:
- '**' - '**'
# branches is needed if tags-ignore is used # branches is needed if tags-ignore is used
@@ -49,6 +50,11 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
# Workaround for failing builds: https://github.com/docker/build-push-action/issues/761#issuecomment-1383822381
# TODO remove workaround when fixed
with:
driver-opts: |
image=moby/buildkit:v0.10.6
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v2 uses: docker/login-action@v2
@@ -86,7 +92,14 @@ jobs:
- name: Image digest - name: Image digest
# TODO upload digests to assets # TODO upload digests to assets
run: | run: |
echo "extract_otp_secrets: ${{ steps.docker_build_qr_reader_latest.outputs.digest }}" echo "extract_otp_secrets digests: ${{ steps.docker_build_qr_reader_latest.outputs.digest }}"
echo "${{ steps.docker_build_qr_reader_latest.outputs.digest }}" > digests.txt
- name: Save docker digests as artifacts
if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v3
with:
name: debian_digests
path: digests.txt
build-and-push-docker-alpine-image: build-and-push-docker-alpine-image:
name: Build Docker Alpine image and push to repositories name: Build Docker Alpine image and push to repositories
@@ -149,8 +162,91 @@ jobs:
build-args: | build-args: |
RUN_TESTS=true RUN_TESTS=true
- name: Image digest
# TODO upload digests to assets
run: |
echo "extract_otp_secrets:only-txt digests: ${{ steps.docker_build_only_txt.outputs.digest }}"
echo "${{ steps.docker_build_qr_reader_latest.outputs.digest }}" > digests.txt
- name: Save docker digests as artifacts
if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v3
with:
name: alpine_digests
path: digests.txt
build-and-push-docker-buster-image:
name: Build Docker Buster image (for PyInstsaller) and push to repositories
# run only when code is compiling and tests are passing
runs-on: ubuntu-latest
# steps to perform in job
steps:
- name: Checkout code
uses: actions/checkout@v3
# avoid building if there are testing errors
- name: Run smoke test
run: |
sudo apt-get install -y libzbar0
python -m pip install --upgrade pip
pip install -U -r requirements-dev.txt
pip install -U .
pytest
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
# setup Docker build action
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
# Workaround for failing builds: https://github.com/docker/build-push-action/issues/761#issuecomment-1383822381
# TODO remove workaround when fixed
with:
driver-opts: |
image=moby/buildkit:v0.10.6
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Packages
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_IO_TOKEN }}
- name: "Build image from Buster and push to GitHub Container Registry"
id: docker_build_buster
if: github.ref == 'refs/heads/master'
uses: docker/build-push-action@v3
with:
platforms: linux/amd64,linux/arm64
# relative path to the place where source code with Dockerfile is located
# TODO file:, move to docker/
context: .
file: Dockerfile
# builder: ${{ steps.buildx.outputs.name }}
build-args: |
BASE_IMAGE=python:3.11-slim-buster
# Note: tags has to be all lower-case
pull: true
tags: |
scit0/extract_otp_secrets:buster
push: true
- name: Image digest - name: Image digest
# TODO upload digests to assets # TODO upload digests to assets
run: | run: |
echo "extract_otp_secrets:only-txt: ${{ steps.docker_build_only_txt.outputs.digest }}" echo "extract_otp_secrets digests: ${{ steps.docker_build_qr_reader_latest.outputs.digest }}"
echo "${{ steps.docker_build_qr_reader_latest.outputs.digest }}" > digests.txt
- name: Save docker digests as artifacts
if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v3
with:
name: buster_digests
path: digests.txt

View File

@@ -1,10 +1,8 @@
name: release name: release
# https://data-dive.com/multi-os-deployment-in-cloud-using-pyinstaller-and-github-actions # https://data-dive.com/multi-os-deployment-in-cloud-using-pyinstaller-and-github-actions
# https://github.com/actions/create-release (archived)
# https://github.com/actions/upload-artifact # https://github.com/actions/upload-artifact
# https://github.com/actions/download-artifact # https://github.com/actions/download-artifact
# https://github.com/actions/upload-release-asset (archived)
# https://github.com/docker/metadata-action # https://github.com/docker/metadata-action
# https://github.com/marketplace/actions/generate-release-hashes # https://github.com/marketplace/actions/generate-release-hashes
@@ -36,18 +34,23 @@ on:
push: push:
tags: tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
pull_request:
schedule:
# Run weekly on default branch
- cron: '47 4 * * 6'
jobs: jobs:
create-release: create-release:
name: Create Release name: Create Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps: steps:
- name: Set meta data - name: Set meta data
id: meta id: meta
# Writing to env with >> $GITHUB_ENV is an alternative # Writing to env with >> $GITHUB_ENV is an alternative
run: | run: |
echo "date=$(TZ=Europe/Bern date +'%d.%m.%Y')" >> $GITHUB_OUTPUT echo "date=$(TZ=Europe/Zurich date +'%d.%m.%Y')" >> $GITHUB_OUTPUT
echo "version=${TAG_NAME/v/}" >> $GITHUB_OUTPUT echo "version=${TAG_NAME/v/}" >> $GITHUB_OUTPUT
echo "tag_name=${{ github.ref_name }}" >> $GITHUB_OUTPUT echo "tag_name=${{ github.ref_name }}" >> $GITHUB_OUTPUT
echo "tag_message=$(git tag -l --format='%(contents:subject)' ${{ github.ref_name }})" >> $GITHUB_OUTPUT echo "tag_message=$(git tag -l --format='%(contents:subject)' ${{ github.ref_name }})" >> $GITHUB_OUTPUT
@@ -68,13 +71,19 @@ jobs:
-d '{"tag_name":"${{ github.ref }}","target_commitish":"master","name":"${{ steps.meta.outputs.version }} - ${{ steps.meta.outputs.date }}","body":"${{ steps.meta.outputs.tag_message }}","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 }}","draft":true,"prerelease":false,"generate_release_notes":true}')
echo upload_url=$(jq '.upload_url' <<< "$response") >> $GITHUB_OUTPUT echo upload_url=$(jq '.upload_url' <<< "$response") >> $GITHUB_OUTPUT
echo $(jq -r '.upload_url' <<< "$response") > release_url.txt echo $(jq -r '.upload_url' <<< "$response") > release_url.txt
echo $(jq -r '.id' <<< "$response") > release_id.txt
- name: Save Release URL File for publish - name: Save Release URL File for publish
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: release_url name: release_url
path: release_url.txt path: release_url.txt
- name: Save asset upload id for publish
uses: actions/upload-artifact@v3
with:
name: release_id
path: release_id.txt
build-and-push-docker-image: build-linux-executable-in-docker:
name: Build Linux release in docker container name: Build Linux release in docker container
# run only when code is compiling and tests are passing # run only when code is compiling and tests are passing
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -101,6 +110,11 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
# Workaround for failing builds: https://github.com/docker/build-push-action/issues/761#issuecomment-1383822381
# TODO remove workaround when fixed
with:
driver-opts: |
image=moby/buildkit:v0.10.6
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v2 uses: docker/login-action@v2
@@ -117,6 +131,8 @@ jobs:
- name: "Build image from Buster and push to GitHub Container Registry" - name: "Build image from Buster and push to GitHub Container Registry"
id: docker_build_buster id: docker_build_buster
# Disable and build in ci_docker for speeding up releases
if: false
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
@@ -130,7 +146,7 @@ jobs:
# Note: tags has to be all lower-case # Note: tags has to be all lower-case
pull: true pull: true
tags: | tags: |
ghcr.io/scito/extract_otp_secrets:buster scit0/extract_otp_secrets:buster
push: true push: true
# # https://stackoverflow.com/a/61155718/1663871 # # https://stackoverflow.com/a/61155718/1663871
@@ -147,14 +163,21 @@ jobs:
- name: Run Pyinstaller in container - name: Run Pyinstaller in container
run: | run: |
# TODO use local docker image https://stackoverflow.com/a/61155718/1663871 # TODO use local docker image https://stackoverflow.com/a/61155718/1663871
docker run --pull always --entrypoint /bin/bash --rm -v "$(pwd)":/files -w /files ghcr.io/scito/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' docker run --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 extract_otp_secrets_linux_x86_64 --distpath /files/dist/ /files/src/extract_otp_secrets.py'
- name: Smoke tests - name: Smoke tests
run: | run: |
dist/extract_otp_secrets_linux_x86_64 -V
dist/extract_otp_secrets_linux_x86_64 -h dist/extract_otp_secrets_linux_x86_64 -h
dist/extract_otp_secrets_linux_x86_64 example_export.png dist/extract_otp_secrets_linux_x86_64 example_export.png
dist/extract_otp_secrets_linux_x86_64 - < example_export.txt dist/extract_otp_secrets_linux_x86_64 - < example_export.txt
dist/extract_otp_secrets_linux_x86_64 --qr ZBAR example_export.png
dist/extract_otp_secrets_linux_x86_64 --qr QREADER example_export.png
dist/extract_otp_secrets_linux_x86_64 --qr QREADER_DEEP example_export.png
dist/extract_otp_secrets_linux_x86_64 --qr CV2 example_export.png
dist/extract_otp_secrets_linux_x86_64 --qr CV2_WECHAT example_export.png
- name: Load Release URL File from release job - name: Load Release URL File from release job
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
name: release_url name: release_url
@@ -162,8 +185,7 @@ jobs:
run: ls -R run: ls -R
- name: Upload Release Asset - name: Upload Release Asset
id: upload-release-asset id: upload-release-asset
# TODO only for tags if: startsWith(github.ref, 'refs/tags/v')
shell: bash
run: | run: |
response=$(curl \ response=$(curl \
-X POST \ -X POST \
@@ -176,8 +198,8 @@ jobs:
--data-binary @dist/extract_otp_secrets_linux_x86_64 \ --data-binary @dist/extract_otp_secrets_linux_x86_64 \
$(cat release_url.txt)=extract_otp_secrets_linux_x86_64) $(cat release_url.txt)=extract_otp_secrets_linux_x86_64)
build: build-native-executables:
name: Build packages name: Build native packages
needs: create-release needs: create-release
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
@@ -189,8 +211,9 @@ jobs:
# TODO add --icon # TODO add --icon
# TODO add --manifest # TODO add --manifest
# TODO find more elegant solution for pyzbar\libiconv.dll and pyzbar\libzbar-64.dll # 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
CMD_BUILD: | 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" --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\file_version_info.txt src\extract_otp_secrets.py
OUT_FILE_NAME: extract_otp_secrets.exe OUT_FILE_NAME: extract_otp_secrets.exe
ASSET_NAME: extract_otp_secrets_win_x86_64.exe ASSET_NAME: extract_otp_secrets_win_x86_64.exe
ASSET_MIME: application/vnd.microsoft.portable-executable ASSET_MIME: application/vnd.microsoft.portable-executable
@@ -218,6 +241,12 @@ jobs:
ASSET_MIME: application/x-executable ASSET_MIME: application/x-executable
UPLOAD: false UPLOAD: false
steps: steps:
- name: Output path
if: runner.os == 'Windows'
run: echo "$($Env:Path)"
- name: List Windir
if: runner.os == 'Windows'
run: ls "$($Env:WinDir)\system32"
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set macos macos_python_path - name: Set macos macos_python_path
# TODO use variable for Python version # TODO use variable for Python version
@@ -245,37 +274,97 @@ jobs:
shell: bash shell: bash
run: | run: |
mkdir -p build/ 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 "s/^([0-9]+).*/\1/") 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 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 }} - name: Build with pyinstaller for ${{ matrix.TARGET }}
run: ${{ matrix.CMD_BUILD }} run: ${{ matrix.CMD_BUILD }}
- name: Smoke tests for generated exe (general) - name: Smoke tests for generated exe (general)
run: | run: |
dist/${{ matrix.OUT_FILE_NAME }} -V
dist/${{ matrix.OUT_FILE_NAME }} -h dist/${{ matrix.OUT_FILE_NAME }} -h
dist/${{ matrix.OUT_FILE_NAME }} example_export.png 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
- name: Smoke tests for generated exe (stdin) - name: Smoke tests for generated exe (stdin)
if: runner.os != 'Windows' if: runner.os != 'Windows'
run: | run: |
dist/${{ matrix.OUT_FILE_NAME }} - < example_export.txt dist/${{ matrix.OUT_FILE_NAME }} - < example_export.txt
- name: Load Release URL File from release job - name: Load Release URL File from release job
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
name: release_url name: release_url
- name: Load Release Id File from release job
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/download-artifact@v3
with:
name: release_id
- name: Display structure of files - name: Display structure of files
run: ls -R run: ls -R
- name: Upload Release Asset - name: Set meta data
id: upload-release-asset id: meta
# TODO only for tags if: startsWith(github.ref, 'refs/tags/v')
if: ${{ matrix.UPLOAD }}
shell: bash shell: bash
run: | run: |
response=$(curl \ echo "release_id=$(cat release_id.txt)" >> $GITHUB_OUTPUT
-X POST \ echo "upload_url=https://uploads.github.com/repos/scito/extract_otp_secrets/releases/$(cat release_id.txt)/assets?name=" >> $GITHUB_OUTPUT
- 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 }}
upload-hashes:
name: Upload hashes
if: startsWith(github.ref, 'refs/tags/v')
needs:
- build-linux-executable-in-docker
- build-native-executables
runs-on: ubuntu-latest
steps:
- name: Load Release Id File from release job
uses: actions/download-artifact@v3
with:
name: release_id
- name: Set meta data
id: meta
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: Calculate and upload hashes from assets
run: |
GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
for asset_url in $(curl \
-H "Accept: application/vnd.github+json" \ -H "Accept: application/vnd.github+json" \
-H "Content-Type: ${{ matrix.ASSET_MIME }}" \ -H "Authorization: Bearer $GITHUB_TOKEN"\
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"\
-H "X-GitHub-Api-Version: 2022-11-28" \ -H "X-GitHub-Api-Version: 2022-11-28" \
--silent \ --silent \
--show-error \ --show-error \
--data-binary @dist/${{ matrix.OUT_FILE_NAME }} \ https://api.github.com/repos/scito/extract_otp_secrets/releases/${{ steps.meta.outputs.release_id }}/assets |
$(cat release_url.txt)=${{ matrix.ASSET_NAME }}) jq -r '.[].url'); do
echo "Download $asset_url"
name=$(curl \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_TOKEN"\
-H "X-GitHub-Api-Version: 2022-11-28" \
--output-dir assets \
-L \
$asset_url |
jq -r '.name')
curl \
-H "Accept: application/octet-stream" \
-H "Authorization: Bearer $GITHUB_TOKEN"\
-H "X-GitHub-Api-Version: 2022-11-28" \
--create-dirs \
--output-dir assets \
-L \
-o $name \
$asset_url
done
(cd assets/ && sha256sum * > ../sha256_hashes.txt)
curl -X POST -H "Accept: application/vnd.github+json" -H "Content-Type: text/plain" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --show-error --data @sha256_hashes.txt ${{ steps.meta.outputs.upload_url }}=sha256_hashes.txt
(cd assets/ && sha512sum * > ../sha512_hashes.txt)
curl -X POST -H "Accept: application/vnd.github+json" -H "Content-Type: text/plain" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --show-error --data @sha512_hashes.txt ${{ steps.meta.outputs.upload_url }}=sha512_hashes.txt

1
.gitignore vendored
View File

@@ -25,3 +25,4 @@ dist_*/
file_version_info_python.txt file_version_info_python.txt
file_version_info_explorer.txt file_version_info_explorer.txt
file_version_info.txt file_version_info.txt
assets/*

View File

@@ -21,6 +21,7 @@ RUN apt-get update && apt-get install -y \
libglib2.0-0 \ libglib2.0-0 \
libsm6 \ libsm6 \
libzbar0 \ libzbar0 \
python3-tk \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir -U -r requirements.txt \ && pip install --no-cache-dir -U -r requirements.txt \
&& if [ "$RUN_TESTS" = "true" ]; then /extract/run_pytest.sh; else echo "Not running tests..."; fi \ && if [ "$RUN_TESTS" = "true" ]; then /extract/run_pytest.sh; else echo "Not running tests..."; fi \

View File

@@ -9,6 +9,7 @@ opencv-contrib-python = "*"
# for macOS: opencv-contrib-python = "<=4.7.0" # for macOS: opencv-contrib-python = "<=4.7.0"
# for PYTHON <= 3.7: typing_extensions = "*" # for PYTHON <= 3.7: typing_extensions = "*"
pillow = "*" pillow = "*"
pyzbar = "*"
protobuf = "*" protobuf = "*"
qrcode = "*" qrcode = "*"
qreader = "<2.0.0" qreader = "<2.0.0"

121
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "41edd4aebe075d6c39d035ec7cb10f0253a3ad21f9b4aa5b9c57deccca87874f" "sha256": "42b14c5eae25b0924354520fe0a26a8d826c905f4613d717f3bfa52e98ed5e8e"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -200,6 +200,7 @@
"sha256:4559628b8192feb25766d954b36a3753baaf5c97c03135aec7e4a026036b475d", "sha256:4559628b8192feb25766d954b36a3753baaf5c97c03135aec7e4a026036b475d",
"sha256:8f4c5264c9c7c6b9f20d01efc52a4eba1ded47d9ba857a94130afe33703eb518" "sha256:8f4c5264c9c7c6b9f20d01efc52a4eba1ded47d9ba857a94130afe33703eb518"
], ],
"index": "pypi",
"version": "==0.1.9" "version": "==0.1.9"
}, },
"qrcode": { "qrcode": {
@@ -247,60 +248,60 @@
"toml" "toml"
], ],
"hashes": [ "hashes": [
"sha256:051afcbd6d2ac39298d62d340f94dbb6a1f31de06dfaf6fcef7b759dd3860c45", "sha256:04481245ef966fbd24ae9b9e537ce899ae584d521dfbe78f89cad003c38ca2ab",
"sha256:0a1890fca2962c4f1ad16551d660b46ea77291fba2cc21c024cd527b9d9c8809", "sha256:0c45948f613d5d18c9ec5eaa203ce06a653334cf1bd47c783a12d0dd4fd9c851",
"sha256:0ee30375b409d9a7ea0f30c50645d436b6f5dfee254edffd27e45a980ad2c7f4", "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265",
"sha256:13250b1f0bd023e0c9f11838bdeb60214dd5b6aaf8e8d2f110c7e232a1bff83b", "sha256:218fe982371ac7387304153ecd51205f14e9d731b34fb0568181abaf7b443ba0",
"sha256:17e01dd8666c445025c29684d4aabf5a90dc6ef1ab25328aa52bedaa95b65ad7", "sha256:29571503c37f2ef2138a306d23e7270687c0efb9cab4bd8038d609b5c2393a3a",
"sha256:19245c249aa711d954623d94f23cc94c0fd65865661f20b7781210cb97c471c0", "sha256:2a60d6513781e87047c3e630b33b4d1e89f39836dac6e069ffee28c4786715f5",
"sha256:1caed2367b32cc80a2b7f58a9f46658218a19c6cfe5bc234021966dc3daa01f0", "sha256:2bf1d5f2084c3932b56b962a683074a3692bce7cabd3aa023c987a2a8e7612f6",
"sha256:1f66862d3a41674ebd8d1a7b6f5387fe5ce353f8719040a986551a545d7d83ea", "sha256:3164d31078fa9efe406e198aecd2a02d32a62fecbdef74f76dad6a46c7e48311",
"sha256:220e3fa77d14c8a507b2d951e463b57a1f7810a6443a26f9b7591ef39047b1b2", "sha256:32df215215f3af2c1617a55dbdfb403b772d463d54d219985ac7cd3bf124cada",
"sha256:276f4cd0001cd83b00817c8db76730938b1ee40f4993b6a905f40a7278103b3a", "sha256:33d1ae9d4079e05ac4cc1ef9e20c648f5afabf1a92adfaf2ccf509c50b85717f",
"sha256:29de916ba1099ba2aab76aca101580006adfac5646de9b7c010a0f13867cba45", "sha256:33ff26d0f6cc3ca8de13d14fde1ff8efe1456b53e3f0273e63cc8b3c84a063d8",
"sha256:2a7f23bbaeb2a87f90f607730b45564076d870f1fb07b9318d0c21f36871932b", "sha256:38da2db80cc505a611938d8624801158e409928b136c8916cd2e203970dde4dc",
"sha256:2c407b1950b2d2ffa091f4e225ca19a66a9bd81222f27c56bd12658fc5ca1209", "sha256:3b155caf3760408d1cb903b21e6a97ad4e2bdad43cbc265e3ce0afb8e0057e73",
"sha256:30b5fec1d34cc932c1bc04017b538ce16bf84e239378b8f75220478645d11fca", "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf",
"sha256:3c2155943896ac78b9b0fd910fb381186d0c345911f5333ee46ac44c8f0e43ab", "sha256:3baf5f126f30781b5e93dbefcc8271cb2491647f8283f20ac54d12161dff080e",
"sha256:411d4ff9d041be08fdfc02adf62e89c735b9468f6d8f6427f8a14b6bb0a85095", "sha256:4b14d5e09c656de5038a3f9bfe5228f53439282abcab87317c9f7f1acb280352",
"sha256:436e103950d05b7d7f55e39beeb4d5be298ca3e119e0589c0227e6d0b01ee8c7", "sha256:51b236e764840a6df0661b67e50697aaa0e7d4124ca95e5058fa3d7cbc240b7c",
"sha256:49640bda9bda35b057b0e65b7c43ba706fa2335c9a9896652aebe0fa399e80e6", "sha256:63ffd21aa133ff48c4dff7adcc46b7ec8b565491bfc371212122dd999812ea1c",
"sha256:4a950f83fd3f9bca23b77442f3a2b2ea4ac900944d8af9993743774c4fdc57af", "sha256:6a43c7823cd7427b4ed763aa7fb63901ca8288591323b58c9cd6ec31ad910f3c",
"sha256:50a6adc2be8edd7ee67d1abc3cd20678987c7b9d79cd265de55941e3d0d56499", "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda",
"sha256:52ab14b9e09ce052237dfe12d6892dd39b0401690856bcfe75d5baba4bfe2831", "sha256:7a726d742816cb3a8973c8c9a97539c734b3a309345236cd533c4883dda05b8d",
"sha256:54f7e9705e14b2c9f6abdeb127c390f679f6dbe64ba732788d3015f7f76ef637", "sha256:7c7c0d0827e853315c9bbd43c1162c006dd808dbbe297db7ae66cd17b07830f0",
"sha256:66e50680e888840c0995f2ad766e726ce71ca682e3c5f4eee82272c7671d38a2", "sha256:7ed681b0f8e8bcbbffa58ba26fcf5dbc8f79e7997595bf071ed5430d8c08d6f3",
"sha256:790e4433962c9f454e213b21b0fd4b42310ade9c077e8edcb5113db0818450cb", "sha256:7ee5c9bb51695f80878faaa5598040dd6c9e172ddcf490382e8aedb8ec3fec8d",
"sha256:7a38362528a9115a4e276e65eeabf67dcfaf57698e17ae388599568a78dcb029", "sha256:8361be1c2c073919500b6601220a6f2f98ea0b6d2fec5014c1d9cfa23dd07038",
"sha256:7b05ed4b35bf6ee790832f68932baf1f00caa32283d66cc4d455c9e9d115aafc", "sha256:8ae125d1134bf236acba8b83e74c603d1b30e207266121e76484562bc816344c",
"sha256:7e109f1c9a3ece676597831874126555997c48f62bddbcace6ed17be3e372de8", "sha256:9817733f0d3ea91bea80de0f79ef971ae94f81ca52f9b66500c6a2fea8e4b4f8",
"sha256:949844af60ee96a376aac1ded2a27e134b8c8d35cc006a52903fc06c24a3296f", "sha256:98b85dd86514d889a2e3dd22ab3c18c9d0019e696478391d86708b805f4ea0fa",
"sha256:95304068686545aa368b35dfda1cdfbbdbe2f6fe43de4a2e9baa8ebd71be46e2", "sha256:9ccb092c9ede70b2517a57382a601619d20981f56f440eae7e4d7eaafd1d1d09",
"sha256:9e662e6fc4f513b79da5d10a23edd2b87685815b337b1a30cd11307a6679148d", "sha256:9d58885215094ab4a86a6aef044e42994a2bd76a446dc59b352622655ba6621b",
"sha256:a9fed35ca8c6e946e877893bbac022e8563b94404a605af1d1e6accc7eb73289", "sha256:b643cb30821e7570c0aaf54feaf0bfb630b79059f85741843e9dc23f33aaca2c",
"sha256:b69522b168a6b64edf0c33ba53eac491c0a8f5cc94fa4337f9c6f4c8f2f5296c", "sha256:bc7c85a150501286f8b56bd8ed3aa4093f4b88fb68c0843d21ff9656f0009d6a",
"sha256:b78729038abea6a5df0d2708dce21e82073463b2d79d10884d7d591e0f385ded", "sha256:beeb129cacea34490ffd4d6153af70509aa3cda20fdda2ea1a2be870dfec8d52",
"sha256:b8c56bec53d6e3154eaff6ea941226e7bd7cc0d99f9b3756c2520fc7a94e6d96", "sha256:c31b75ae466c053a98bf26843563b3b3517b8f37da4d47b1c582fdc703112bc3",
"sha256:b9727ac4f5cf2cbf87880a63870b5b9730a8ae3a4a360241a0fdaa2f71240ff0", "sha256:c4e4881fa9e9667afcc742f0c244d9364d197490fbc91d12ac3b5de0bf2df146",
"sha256:ba3027deb7abf02859aca49c865ece538aee56dcb4871b4cced23ba4d5088904", "sha256:c5b15ed7644ae4bee0ecf74fee95808dcc34ba6ace87e8dfbf5cb0dc20eab45a",
"sha256:be9fcf32c010da0ba40bf4ee01889d6c737658f4ddff160bd7eb9cac8f094b21", "sha256:d12d076582507ea460ea2a89a8c85cb558f83406c8a41dd641d7be9a32e1274f",
"sha256:c18d47f314b950dbf24a41787ced1474e01ca816011925976d90a88b27c22b89", "sha256:d248cd4a92065a4d4543b8331660121b31c4148dd00a691bfb7a5cdc7483cfa4",
"sha256:c76a3075e96b9c9ff00df8b5f7f560f5634dffd1658bafb79eb2682867e94f78", "sha256:d47dd659a4ee952e90dc56c97d78132573dc5c7b09d61b416a9deef4ebe01a0c",
"sha256:cbfcba14a3225b055a28b3199c3d81cd0ab37d2353ffd7f6fd64844cebab31ad", "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75",
"sha256:d254666d29540a72d17cc0175746cfb03d5123db33e67d1020e42dae611dc196", "sha256:da9b41d4539eefd408c46725fb76ecba3a50a3367cafb7dea5f250d0653c1040",
"sha256:d66187792bfe56f8c18ba986a0e4ae44856b1c645336bd2c776e3386da91e1dd", "sha256:db61a79c07331e88b9a9974815c075fbd812bc9dbc4dc44b366b5368a2936063",
"sha256:d8d04e755934195bdc1db45ba9e040b8d20d046d04d6d77e71b3b34a8cc002d0", "sha256:ddb726cb861c3117a553f940372a495fe1078249ff5f8a5478c0576c7be12050",
"sha256:d8f3e2e0a1d6777e58e834fd5a04657f66affa615dae61dd67c35d1568c38882", "sha256:ded59300d6330be27bc6cf0b74b89ada58069ced87c48eaf9344e5e84b0072f7",
"sha256:e057e74e53db78122a3979f908973e171909a58ac20df05c33998d52e6d35757", "sha256:e2617759031dae1bf183c16cef8fcfb3de7617f394c813fa5e8e46e9b82d4222",
"sha256:e4ce984133b888cc3a46867c8b4372c7dee9cee300335e2925e197bcd45b9e16", "sha256:e5cdbb5cafcedea04924568d990e20ce7f1945a1dd54b560f879ee2d57226912",
"sha256:ea76dbcad0b7b0deb265d8c36e0801abcddf6cc1395940a24e3595288b405ca0", "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801",
"sha256:ecb0f73954892f98611e183f50acdc9e21a4653f294dfbe079da73c6378a6f47", "sha256:ef382417db92ba23dfb5864a3fc9be27ea4894e86620d342a116b243ade5d35d",
"sha256:ef14d75d86f104f03dea66c13188487151760ef25dd6b2dbd541885185f05f40", "sha256:f2cba5c6db29ce991029b5e4ac51eb36774458f0a3b8d3137241b32d1bb91f06",
"sha256:f26648e1b3b03b6022b48a9b910d0ae209e2d51f50441db5dce5b530fad6d9b1", "sha256:f5b4198d85a3755d27e64c52f8c95d6333119e49fd001ae5798dac872c95e0f8",
"sha256:f67472c09a0c7486e27f3275f617c964d25e35727af952869dd496b9b5b7f6a3" "sha256:ffeeb38ee4a80a30a6877c5c4c359e5498eec095878f1581453202bfacc8fbc2"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==7.0.5" "version": "==7.1.0"
}, },
"dill": { "dill": {
"hashes": [ "hashes": [
@@ -336,11 +337,11 @@
}, },
"isort": { "isort": {
"hashes": [ "hashes": [
"sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6", "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504",
"sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b" "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"
], ],
"markers": "python_full_version >= '3.7.0'", "markers": "python_full_version >= '3.8.0'",
"version": "==5.11.4" "version": "==5.12.0"
}, },
"lazy-object-proxy": { "lazy-object-proxy": {
"hashes": [ "hashes": [
@@ -545,11 +546,11 @@
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:6f590d76b713d5de4e49fe4fbca24474469f53c83632d5d0fd056f7ff7e8112b", "sha256:883131c5b6efa70b9101c7ef30b2b7b780a4283d5fc1616383cdf22c83cbefe6",
"sha256:ac4008d396bc9cd983ea483cb7139c0240a07bbc74ffb6232fceffedc6cf03a8" "sha256:9d790961ba6219e9ff7d9557622d2fe136816a264dd01d5997cfc057d804853d"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==66.1.1" "version": "==67.0.0"
}, },
"setuptools-git-versioning": { "setuptools-git-versioning": {
"hashes": [ "hashes": [

View File

@@ -3,19 +3,20 @@
[![CI tests](https://github.com/scito/extract_otp_secrets/actions/workflows/ci.yml/badge.svg)](https://github.com/scito/extract_otp_secrets/actions/workflows/ci.yml) [![CI tests](https://github.com/scito/extract_otp_secrets/actions/workflows/ci.yml/badge.svg)](https://github.com/scito/extract_otp_secrets/actions/workflows/ci.yml)
[![CI docker](https://github.com/scito/extract_otp_secrets/actions/workflows/ci_docker.yml/badge.svg)](https://github.com/scito/extract_otp_secrets/actions/workflows/ci_docker.yml) [![CI docker](https://github.com/scito/extract_otp_secrets/actions/workflows/ci_docker.yml/badge.svg)](https://github.com/scito/extract_otp_secrets/actions/workflows/ci_docker.yml)
![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen) ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)
<!-- ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/protobuf)
[![GitHub Pipenv locked Python version](https://img.shields.io/github/pipenv/locked/python-version/scito/extract_otp_secrets)](https://github.com/scito/extract_otp_secrets/blob/master/Pipfile.lock)
![protobuf version](https://img.shields.io/badge/protobuf-4.21.12-informational)-->
![python versions](https://img.shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue)
[![License](https://img.shields.io/github/license/scito/extract_otp_secrets)](https://github.com/scito/extract_otp_secrets/blob/master/LICENSE) [![License](https://img.shields.io/github/license/scito/extract_otp_secrets)](https://github.com/scito/extract_otp_secrets/blob/master/LICENSE)
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/scito/extract_otp_secrets?sort=semver)](https://github.com/scito/extract_otp_secrets/releases/latest) [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/scito/extract_otp_secrets?sort=semver)](https://github.com/scito/extract_otp_secrets/releases/latest)
[![Download executable](https://img.shields.io/badge/download-exe-blue)](https://github.com/scito/extract_otp_secrets/releases/latest) ![python versions](https://img.shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue)
[![Docker image](https://img.shields.io/badge/docker-image-blue)](https://hub.docker.com/repository/docker/scit0/extract_otp_secrets/general)
[![Linux](https://img.shields.io/badge/os-linux-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest) [![Linux](https://img.shields.io/badge/os-linux-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest)
[![Windows](https://img.shields.io/badge/os-windows-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest) [![Windows](https://img.shields.io/badge/os-windows-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest)
[![MacOS](https://img.shields.io/badge/os-macos-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest) [![MacOS](https://img.shields.io/badge/os-macos-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest)
[![Docker image](https://img.shields.io/badge/docker-image-blue)](https://hub.docker.com/repository/docker/scit0/extract_otp_secrets/general) [![Download executable](https://img.shields.io/badge/download-exe-blue)](https://github.com/scito/extract_otp_secrets/releases/latest)
<!-- [![Github all releases](https://img.shields.io/github/downloads/scito/extract_otp_secrets/total.svg)](https://GitHub.com/scito/extract_otp_secrets/releases/) -->
[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua)
<!-- ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/protobuf)
[![GitHub Pipenv locked Python version](https://img.shields.io/github/pipenv/locked/python-version/scito/extract_otp_secrets)](https://github.com/scito/extract_otp_secrets/blob/master/Pipfile.lock)
![protobuf version](https://img.shields.io/badge/protobuf-4.21.12-informational)-->
<!-- [![Github all releases](https://img.shields.io/github/downloads/scito/extract_otp_secrets/total.svg)](https://GitHub.com/scito/extract_otp_secrets/releases/) -->
--- ---
@@ -35,13 +36,12 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or
## Table of contents ## Table of contents
- [Download binary executable (🆕 since v2.1)](#download-binary-executable--since-v21)
- [Usage](#usage) - [Usage](#usage)
- [Capture QR codes from camera (🆕 since version 2.0)](#capture-qr-codes-from-camera--since-version-20) - [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) - [With builtin QR decoder from image files (🆕 since version 2.0)](#with-builtin-qr-decoder-from-image-files--since-version-20)
- [With external QR decoder app from text files](#with-external-qr-decoder-app-from-text-files) - [With external QR decoder app from text files](#with-external-qr-decoder-app-from-text-files)
- [Installation](#installation) - [Installation of Python script (recommend for developers or advanced users)](#installation-of-python-script-recommend-for-developers-or-advanced-users)
- [Download binary executable (🆕 since v2.1)](#download-binary-executable--since-v21)
- [Run as script (recommend for developers or advanced users)](#run-as-script-recommend-for-developers-or-advanced-users)
- [Installation of shared system libraries](#installation-of-shared-system-libraries) - [Installation of shared system libraries](#installation-of-shared-system-libraries)
- [Program help: arguments and options](#program-help-arguments-and-options) - [Program help: arguments and options](#program-help-arguments-and-options)
- [Examples](#examples) - [Examples](#examples)
@@ -86,6 +86,26 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or
- [Related projects](#related-projects) - [Related projects](#related-projects)
</details> </details>
## Download 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
: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.
: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: 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.
## Usage ## Usage
### Capture QR codes from camera (🆕 since version 2.0) ### Capture QR codes from camera (🆕 since version 2.0)
@@ -97,10 +117,6 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or
``` ```
extract_otp_secrets extract_otp_secrets
``` ```
or
```
python src/extract_otp_secrets.py
```
![CV2 Capture from camera screenshot](docs/cv2_capture_screenshot.png) ![CV2 Capture from camera screenshot](docs/cv2_capture_screenshot.png)
@@ -110,6 +126,14 @@ Detected QR codes are surrounded with a frame. The color of the frame indicates
* Red: The QR code is detected and decoded, but could not be successfully extracted. This is the case if a QR code not containing OTP data is captured. * Red: The QR code is detected and decoded, but could not be successfully extracted. This is the case if a QR code not containing OTP data is captured.
* Magenta: The QR code is detected, but could not be decoded. The QR code should be presented better to the camera or another QR reader could be used. * Magenta: The QR code is detected, but could not be decoded. The QR code should be presented better to the camera or another QR reader could be used.
Key commands:
* Space: change QR code reader
* C: save as csv file (🆕 since v2.2)
* J: save as json file (🆕 since v2.2)
* K: save as KeePass csv file (🆕 since v2.2)
* ESC, ENTER, Q: quit the program
The secrets are printed by default to the console. [Set program parameters](#program-help-arguments-and-options) for other types of output, e.g. `--csv exported_secrets.csv`. The secrets are printed by default to the console. [Set program parameters](#program-help-arguments-and-options) for other types of output, e.g. `--csv exported_secrets.csv`.
### With builtin QR decoder from image files (🆕 since version 2.0) ### With builtin QR decoder from image files (🆕 since version 2.0)
@@ -120,7 +144,7 @@ The secrets are printed by default to the console. [Set program parameters](#pro
4. Transfer the images files to the computer where his script is installed. 4. Transfer the images files to the computer where his script is installed.
5. Call this script with the file as input: 5. Call this script with the file as input:
``` ```
python src/extract_otp_secrets.py example_export.png extract_otp_secrets example_export.png
``` ```
6. Remove unencrypted files with secrets from your computer and mobile. 6. Remove unencrypted files with secrets from your computer and mobile.
@@ -133,22 +157,11 @@ python src/extract_otp_secrets.py example_export.png
5. Transfer the file to the computer where his script is installed. 5. Transfer the file to the computer where his script is installed.
6. Call this script with the file as input: 6. Call this script with the file as input:
``` ```
python src/extract_otp_secrets.py example_export.txt extract_otp_secrets example_export.txt
``` ```
7. Remove unencrypted files with secrets from your computer and mobile. 7. Remove unencrypted files with secrets from your computer and mobile.
## Installation ## Installation of Python script (recommend for developers or advanced users)
### Download 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
✅ Everything is just packed in one executable.
✅ No installation needed, neither Python nor dependencies have to be installed.
✅ Easy and convenient
### Run as script (recommend for developers or advanced users)
```bash ```bash
git clone https://github.com/scito/extract_otp_secrets.git git clone https://github.com/scito/extract_otp_secrets.git
@@ -287,6 +300,10 @@ python extract_otp_secrets.py = < example_export.png</pre>
* QREADER_DEEP: [QReader](https://github.com/Eric-Canas/QReader) - very slow in GUI * QREADER_DEEP: [QReader](https://github.com/Eric-Canas/QReader) - very slow in GUI
* CV2: [QRCodeDetector](https://docs.opencv.org/4.x/de/dc3/classcv_1_1QRCodeDetector.html) * CV2: [QRCodeDetector](https://docs.opencv.org/4.x/de/dc3/classcv_1_1QRCodeDetector.html)
* CV2_WECHAT: [WeChatQRCode](https://docs.opencv.org/4.x/dd/d63/group__wechat__qrcode.html) * CV2_WECHAT: [WeChatQRCode](https://docs.opencv.org/4.x/dd/d63/group__wechat__qrcode.html)
* Program usable as pure GUI application without any command line switches (🆕 since v2.2)
* Save otp secrets as csv file (🆕 since v2.2)
* Save otp secrets as json file (🆕 since v2.2)
* Save otp secrets as KeePass csv file(s) (🆕 since v2.2)
* Supports [TOTP](https://www.ietf.org/rfc/rfc6238.txt) and [HOTP](https://www.ietf.org/rfc/rfc4226.txt) standards * Supports [TOTP](https://www.ietf.org/rfc/rfc6238.txt) and [HOTP](https://www.ietf.org/rfc/rfc4226.txt) standards
* Generates QR codes * Generates QR codes
* Exports to various formats: * Exports to various formats:
@@ -618,7 +635,7 @@ Output is executable `dist/extract_otp_secrets`
#### Windows #### Windows
``` ```
pyinstaller -y --add-data "%pythonLocation%\__yolo_v3_qr_detector;__yolo_v3_qr_detector" --add-binary "%pythonLocation%\pyzbar\libiconv.dll;pyzbar" --add-binary "%pythonLocation%\pyzbar\libzbar-64.dll;pyzbar" --onefile --version-file build\file_version_info.txt src\extract_otp_secrets.py pyinstaller -y --add-data "%pythonLocation%\__yolo_v3_qr_detector;__yolo_v3_qr_detector" --add-binary "%pythonLocation%\pyzbar\libiconv.dll;pyzbar" --add-binary "%pythonLocation%\pyzbar\libzbar-64.dll;pyzbar" --add-binary "%windir%\system32\msvcr120.dll;pyzbar" --add-binary "%windir%\system32\msvcp120.dll;pyzbar" --add-binary "%windir%\system32\vcamp120.dll;pyzbar" --add-binary "%windir%\system32\vcomp120.dll;pyzbar" --add-binary "%windir%\system32\vccorlib120.dll;pyzbar" --add-binary "%windir%\system32\mfc120.dll;pyzbar" --add-binary "%windir%\system32\mfc120u.dll;pyzbar" --add-binary "%windir%\system32\mfc120chs.dll;pyzbar" --add-binary "%windir%\system32\mfc120cht.dll;pyzbar" --add-binary "%windir%\system32\mfc120deu.dll;pyzbar" --add-binary "%windir%\system32\mfc120enu.dll;pyzbar" --add-binary "%windir%\system32\mfc120esn.dll;pyzbar" --add-binary "%windir%\system32\mfc120fra.dll;pyzbar" --add-binary "%windir%\system32\mfc120ita.dll;pyzbar" --add-binary "%windir%\system32\mfc120jpn.dll;pyzbar" --add-binary "%windir%\system32\mfc120kor.dll;pyzbar" --add-binary "%windir%\system32\mfc120rus.dll;pyzbar" --onefile --version-file build\file_version_info.txt src\extract_otp_secrets.py
``` ```
Output is `dist\extract_otp_secrets.exe` Output is `dist\extract_otp_secrets.exe`

View File

@@ -3,13 +3,12 @@ Generate from file: README.md
## Table of contents ## Table of contents
- [Table of contents](#table-of-contents) - [Table of contents](#table-of-contents)
- [Download binary executable (🆕 since v2.1)](#download-binary-executable--since-v21)
- [Usage](#usage) - [Usage](#usage)
- [Capture QR codes from camera (🆕 since version 2.0)](#capture-qr-codes-from-camera--since-version-20) - [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) - [With builtin QR decoder from image files (🆕 since version 2.0)](#with-builtin-qr-decoder-from-image-files--since-version-20)
- [With external QR decoder app from text files](#with-external-qr-decoder-app-from-text-files) - [With external QR decoder app from text files](#with-external-qr-decoder-app-from-text-files)
- [Installation](#installation) - [Installation of Python script (recommend for developers or advanced users)](#installation-of-python-script-recommend-for-developers-or-advanced-users)
- [Download binary executable (🆕 since v2.1)](#download-binary-executable--since-v21)
- [Run as script (recommend for developers or advanced users)](#run-as-script-recommend-for-developers-or-advanced-users)
- [Installation of shared system libraries](#installation-of-shared-system-libraries) - [Installation of shared system libraries](#installation-of-shared-system-libraries)
- [Program help: arguments and options](#program-help-arguments-and-options) - [Program help: arguments and options](#program-help-arguments-and-options)
- [Examples](#examples) - [Examples](#examples)

View File

@@ -1,4 +1,5 @@
colorama>=0.4.6 colorama>=0.4.6
importlib_metadata; python_version<='3.7'
opencv-contrib-python; sys_platform != 'darwin' opencv-contrib-python; sys_platform != 'darwin'
opencv-contrib-python<=4.7.0; sys_platform == 'darwin' opencv-contrib-python<=4.7.0; sys_platform == 'darwin'
Pillow Pillow
@@ -7,4 +8,3 @@ pyzbar
qrcode qrcode
qreader<2.0.0 qreader<2.0.0
typing_extensions; python_version<='3.7' typing_extensions; python_version<='3.7'
importlib_metadata; python_version<='3.7'

View File

@@ -65,11 +65,20 @@ else:
debug_mode = '-d' in sys.argv[1:] or '--debug' in sys.argv[1:] debug_mode = '-d' in sys.argv[1:] or '--debug' in sys.argv[1:]
headless: bool = False
try: try:
import cv2 # type: ignore # TODO use cv2 types if available import cv2 # type: ignore # TODO use cv2 types if available
import numpy as np # TODO use numpy types if available import numpy as np # TODO use numpy types if available
try:
import tkinter
import tkinter.filedialog
import tkinter.messagebox
except ImportError:
headless = True
try: try:
import pyzbar.pyzbar as zbar # type: ignore import pyzbar.pyzbar as zbar # type: ignore
from qreader import QReader # type: ignore from qreader import QReader # type: ignore
@@ -143,6 +152,7 @@ quiet: bool = False
colored: bool = True colored: bool = True
executable: bool = False executable: bool = False
__version__: str __version__: str
tk_root: tkinter.Tk
def sys_main() -> None: def sys_main() -> None:
@@ -150,7 +160,7 @@ def sys_main() -> None:
def main(sys_args: list[str]) -> None: def main(sys_args: list[str]) -> None:
global executable global executable, tk_root, headless
# allow to use sys.stdout with with (avoid closing) # allow to use sys.stdout with with (avoid closing)
sys.stdout.close = lambda: None # type: ignore sys.stdout.close = lambda: None # type: ignore
# set encoding to utf-8, needed for Windows # set encoding to utf-8, needed for Windows
@@ -164,6 +174,13 @@ def main(sys_args: list[str]) -> None:
# https://pyinstaller.org/en/stable/runtime-information.html#run-time-information # https://pyinstaller.org/en/stable/runtime-information.html#run-time-information
executable = getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS') executable = getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS')
if qreader_available and not headless:
try:
tk_root = tkinter.Tk()
tk_root.withdraw()
except tkinter.TclError:
headless = True
args = parse_args(sys_args) args = parse_args(sys_args)
if colored: if colored:
@@ -174,9 +191,10 @@ def main(sys_args: list[str]) -> None:
sys.exit(0 if do_debug_checks() else 1) sys.exit(0 if do_debug_checks() else 1)
otps = extract_otps(args) otps = extract_otps(args)
write_csv(args, otps)
write_keepass_csv(args, otps) write_csv(args.csv, otps)
write_json(args, otps) write_keepass_csv(args.keepass, otps)
write_json(args.json, otps)
# workaround for PYTHON <= 3.9 use: pb.MigrationPayload | None # workaround for PYTHON <= 3.9 use: pb.MigrationPayload | None
@@ -362,15 +380,16 @@ def extract_otps_from_camera(args: Args) -> Otps:
qr_mode = next_qr_mode(qr_mode) qr_mode = next_qr_mode(qr_mode)
continue continue
cv2_print_text(img, f"Mode: {qr_mode.name} (Hit space to change)", 0, TextPosition.LEFT, FONT_COLOR, 20) cv2_print_text(img, f"Mode: {qr_mode.name} (Hit SPACE to change)", 0, TextPosition.LEFT, FONT_COLOR, 20)
cv2_print_text(img, "Hit ESC to quit", 1, TextPosition.LEFT, FONT_COLOR, 17) cv2_print_text(img, "Press ESC to quit", 1, TextPosition.LEFT, FONT_COLOR, 17)
cv2_print_text(img, "Press C/J/K to save as csv/json/keepass file", 2, TextPosition.LEFT, FONT_COLOR, None)
cv2_print_text(img, f"{len(otp_urls)} QR code{'s'[:len(otp_urls) != 1]} captured", 0, TextPosition.RIGHT, FONT_COLOR) cv2_print_text(img, f"{len(otp_urls)} QR code{'s'[:len(otp_urls) != 1]} captured", 0, TextPosition.RIGHT, FONT_COLOR)
cv2_print_text(img, f"{len(otps)} otp{'s'[:len(otps) != 1]} extracted", 1, TextPosition.RIGHT, FONT_COLOR) cv2_print_text(img, f"{len(otps)} otp{'s'[:len(otps) != 1]} extracted", 1, TextPosition.RIGHT, FONT_COLOR)
cv2.imshow(WINDOW_NAME, img) cv2.imshow(WINDOW_NAME, img)
quit, qr_mode = cv2_handle_pressed_keys(qr_mode) quit, qr_mode = cv2_handle_pressed_keys(qr_mode, otps)
if quit: if quit:
break break
@@ -416,12 +435,48 @@ def cv2_print_text(img: Any, text: str, line_number: int, position: TextPosition
cv2.putText(img, out_text, pos, FONT, FONT_SCALE, color, FONT_THICKNESS, FONT_LINE_STYLE) cv2.putText(img, out_text, pos, FONT, FONT_SCALE, color, FONT_THICKNESS, FONT_LINE_STYLE)
def cv2_handle_pressed_keys(qr_mode: QRMode) -> Tuple[bool, QRMode]: def cv2_handle_pressed_keys(qr_mode: QRMode, otps: Otps) -> Tuple[bool, QRMode]:
key = cv2.waitKey(1) & 0xFF key = cv2.waitKey(1) & 0xFF
quit = False quit = False
if key == 27 or key == ord('q') or key == 13: if key == 27 or key == ord('q') or key == ord('Q') or key == 13:
# ESC or Enter or q pressed # ESC or Enter or q pressed
quit = True quit = True
elif (key == ord('c') or key == ord('C')) and is_not_headless():
if has_no_otps_show_warning(otps):
pass
else:
file_name = tkinter.filedialog.asksaveasfilename(
title="Save extracted otp secrets as CSV",
defaultextension='.csv',
filetypes=[('CSV', '*.csv'), ('All', '*.*')]
)
tk_root.update()
if len(file_name) > 0:
write_csv(file_name, otps)
elif (key == ord('j') or key == ord('J')) and is_not_headless():
if has_no_otps_show_warning(otps):
pass
else:
file_name = tkinter.filedialog.asksaveasfilename(
title="Save extracted otp secrets as JSON",
defaultextension='.json',
filetypes=[('JSON', '*.json'), ('All', '*.*')]
)
tk_root.update()
if len(file_name) > 0:
write_json(file_name, otps)
elif (key == ord('k') or key == ord('K')) and is_not_headless():
if has_no_otps_show_warning(otps):
pass
else:
file_name = tkinter.filedialog.asksaveasfilename(
title="Save extracted otp secrets as KeePass CSV file(s)",
defaultextension='.csv',
filetypes=[('CSV', '*.csv'), ('All', '*.*')]
)
tk_root.update()
if len(file_name) > 0:
write_keepass_csv(file_name, otps)
elif key == 32: elif key == 32:
qr_mode = next_qr_mode(qr_mode) qr_mode = next_qr_mode(qr_mode)
if verbose >= LogLevel.MORE_VERBOSE: print(f"QR reading mode: {qr_mode}") if verbose >= LogLevel.MORE_VERBOSE: print(f"QR reading mode: {qr_mode}")
@@ -626,22 +681,22 @@ def print_qr(args: Args, otp_url: str) -> None:
qr.print_ascii() qr.print_ascii()
def write_csv(args: Args, otps: Otps) -> None: def write_csv(file: str, otps: Otps) -> None:
if args.csv and len(otps) > 0: if file and len(file) > 0 and len(otps) > 0:
with open_file_or_stdout_for_csv(args.csv) as outfile: with open_file_or_stdout_for_csv(file) as outfile:
writer = csv.DictWriter(outfile, otps[0].keys()) writer = csv.DictWriter(outfile, otps[0].keys())
writer.writeheader() writer.writeheader()
writer.writerows(otps) writer.writerows(otps)
if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to csv {args.csv}") if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to csv {file}")
def write_keepass_csv(args: Args, otps: Otps) -> None: def write_keepass_csv(file: str, otps: Otps) -> None:
if args.keepass and len(otps) > 0: if file and len(file) > 0 and len(otps) > 0:
has_totp = has_otp_type(otps, 'totp') has_totp = has_otp_type(otps, 'totp')
has_hotp = has_otp_type(otps, 'hotp') has_hotp = has_otp_type(otps, 'hotp')
if args.keepass != '-': if file != '-':
otp_filename_totp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "totp") otp_filename_totp = file if has_totp != has_hotp else add_pre_suffix(file, "totp")
otp_filename_hotp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "hotp") otp_filename_hotp = file if has_totp != has_hotp else add_pre_suffix(file, "hotp")
else: else:
otp_filename_totp = otp_filename_hotp = '-' otp_filename_totp = otp_filename_hotp = '-'
if has_totp: if has_totp:
@@ -653,9 +708,9 @@ def write_keepass_csv(args: Args, otps: Otps) -> None:
if count_hotp_entries: print(f"Exported {count_hotp_entries} hotp entrie{'s'[:count_hotp_entries != 1]} to keepass csv file {otp_filename_hotp}") if count_hotp_entries: print(f"Exported {count_hotp_entries} hotp entrie{'s'[:count_hotp_entries != 1]} to keepass csv file {otp_filename_hotp}")
def write_keepass_totp_csv(otp_filename: str, otps: Otps) -> int: def write_keepass_totp_csv(file: str, otps: Otps) -> int:
count_entries = 0 count_entries = 0
with open_file_or_stdout_for_csv(otp_filename) as outfile: with open_file_or_stdout_for_csv(file) as outfile:
writer = csv.DictWriter(outfile, ["Title", "User Name", "TimeOtp-Secret-Base32", "Group"]) writer = csv.DictWriter(outfile, ["Title", "User Name", "TimeOtp-Secret-Base32", "Group"])
writer.writeheader() writer.writeheader()
for otp in otps: for otp in otps:
@@ -670,9 +725,9 @@ def write_keepass_totp_csv(otp_filename: str, otps: Otps) -> int:
return count_entries return count_entries
def write_keepass_htop_csv(otp_filename: str, otps: Otps) -> int: def write_keepass_htop_csv(file: str, otps: Otps) -> int:
count_entries = 0 count_entries = 0
with open_file_or_stdout_for_csv(otp_filename) as outfile: with open_file_or_stdout_for_csv(file) as outfile:
writer = csv.DictWriter(outfile, ["Title", "User Name", "HmacOtp-Secret-Base32", "HmacOtp-Counter", "Group"]) writer = csv.DictWriter(outfile, ["Title", "User Name", "HmacOtp-Secret-Base32", "HmacOtp-Counter", "Group"])
writer.writeheader() writer.writeheader()
for otp in otps: for otp in otps:
@@ -688,11 +743,11 @@ def write_keepass_htop_csv(otp_filename: str, otps: Otps) -> int:
return count_entries return count_entries
def write_json(args: Args, otps: Otps) -> None: def write_json(file: str, otps: Otps) -> None:
if args.json: if file and len(file) > 0:
with open_file_or_stdout(args.json) as outfile: with open_file_or_stdout(file) as outfile:
json.dump(otps, outfile, indent=4) json.dump(otps, outfile, indent=4)
if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to json {args.json}") if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to json {file}")
def has_otp_type(otps: Otps, otp_type: str) -> bool: def has_otp_type(otps: Otps, otp_type: str) -> bool:
@@ -729,6 +784,13 @@ def check_file_exists(filename: str) -> None:
f"\ninput file: {filename}") f"\ninput file: {filename}")
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
return len(otps) == 0
def is_binary(line: str) -> bool: def is_binary(line: str) -> bool:
try: try:
line.startswith('#') line.startswith('#')
@@ -751,6 +813,12 @@ def do_debug_checks() -> bool:
return True return True
def is_not_headless() -> bool:
if headless:
log_warn(f"Cannot open dialog in headless mode")
return not headless
class PrintVersionAction(argparse.Action): class PrintVersionAction(argparse.Action):
def __init__(self, option_strings: Sequence[str], dest: str, nargs: int = 0, **kwargs: Any) -> None: def __init__(self, option_strings: Sequence[str], dest: str, nargs: int = 0, **kwargs: Any) -> None:
super().__init__(option_strings, dest, nargs, **kwargs) super().__init__(option_strings, dest, nargs, **kwargs)