release: restore ZUPT and harden source-only 5.2.2
This commit is contained in:
parent
74e393ba3e
commit
ff99770bd0
205 changed files with 19627 additions and 13215 deletions
27
.gitattributes
vendored
Normal file
27
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.cmd text eol=crlf
|
||||||
|
*.ps1 text eol=crlf
|
||||||
|
*.s text eol=lf
|
||||||
|
*.S text eol=lf
|
||||||
|
*.jazz text eol=lf
|
||||||
|
|
||||||
|
*.png binary
|
||||||
|
*.ico binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.webp binary
|
||||||
|
*.zip binary
|
||||||
|
*.gz binary
|
||||||
|
*.xz binary
|
||||||
|
*.bz2 binary
|
||||||
|
*.zst binary
|
||||||
|
|
||||||
|
# These downstream recipes pin the checksum of the release tarball itself.
|
||||||
|
# Excluding them from `git archive` avoids a self-referential checksum while
|
||||||
|
# keeping every recipe versioned in Git and available to its package manager.
|
||||||
|
/packaging/aur/** export-ignore
|
||||||
|
/packaging/homebrew/** export-ignore
|
||||||
|
/packaging/guix/** export-ignore
|
||||||
777
.github/workflows/ci.yml
vendored
777
.github/workflows/ci.yml
vendored
|
|
@ -1,254 +1,613 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
#
|
|
||||||
# Zupt CI matrix.
|
|
||||||
#
|
|
||||||
# Mirrors the project's local-verification protocol from PROMPT.md §6:
|
|
||||||
# 1. Plain GCC build
|
|
||||||
# 2. Plain Clang build
|
|
||||||
# 3. Strict GCC (full warning set)
|
|
||||||
# 4. Strict Clang (full warning set)
|
|
||||||
# 5. ASAN + UBSAN
|
|
||||||
# 6. Full regression suite (12 suites: audit, dedup, path-traversal,
|
|
||||||
# argument-order, block-swap, F-08, F-09 byte sweep, F-10, F-11,
|
|
||||||
# F-12, packaging syntax, dist reproducibility)
|
|
||||||
# 7. License header audit
|
|
||||||
# 8. `make dist` reproducibility (two runs, sha256 must match)
|
|
||||||
# 9. aarch64 cross-test via QEMU emulation
|
|
||||||
# 10. Automatic release on git tag push
|
|
||||||
|
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches:
|
||||||
tags: ['v*']
|
- master
|
||||||
|
- 'codex/**'
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [master]
|
branches:
|
||||||
|
- master
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: ${{ github.event_name != 'workflow_dispatch' && !startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ─── Plain build + test, exactly as a user would do it ───
|
source-policy:
|
||||||
|
name: Source-only, license, shell and secret policy
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- name: Check out all refs without LFS or submodules
|
||||||
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: false
|
||||||
|
submodules: false
|
||||||
|
|
||||||
|
- name: Install audit tools
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y \
|
||||||
|
dpkg-dev file git-lfs libarchive-tools libxml2-utils make python3 ruby \
|
||||||
|
shellcheck unzip
|
||||||
|
|
||||||
|
- name: Audit tracked files, worktree and HEAD archive
|
||||||
|
run: bash scripts/check-source-only.sh
|
||||||
|
|
||||||
|
- name: Exercise positive and negative scanner fixtures
|
||||||
|
run: bash tests/test_source_only.sh
|
||||||
|
|
||||||
|
- name: Audit license headers
|
||||||
|
run: make WITH_SDK=0 WITH_PQBOX=0 audit-licenses
|
||||||
|
|
||||||
|
- name: Validate release packaging metadata
|
||||||
|
run: bash tests/test_packaging_syntax.sh
|
||||||
|
|
||||||
|
- name: ShellCheck release and source-policy scripts
|
||||||
|
run: |
|
||||||
|
shellcheck \
|
||||||
|
packaging/build-deb.sh \
|
||||||
|
packaging/build-rpm.sh \
|
||||||
|
packaging/build-appimage.sh \
|
||||||
|
packaging/build-dmg.sh \
|
||||||
|
packaging/build-gui-appimage.sh \
|
||||||
|
packaging/build-gui-deb.sh \
|
||||||
|
packaging/build-gui-rpm.sh \
|
||||||
|
packaging/opensuse/source-audit.sh \
|
||||||
|
scripts/check-source-only.sh \
|
||||||
|
scripts/export-opensuse-package.sh \
|
||||||
|
scripts/test-installed-zupt.sh \
|
||||||
|
tests/test_atomic_archive_output.sh \
|
||||||
|
tests/test_authenticated_dedup_reorder.sh \
|
||||||
|
tests/test_benchmark_temp_safety.sh \
|
||||||
|
tests/test_block_type_confusion.sh \
|
||||||
|
tests/test_disk_device_capacity.sh \
|
||||||
|
tests/test_f09_preface.sh \
|
||||||
|
tests/test_legacy_disk_5_2_1.sh \
|
||||||
|
tests/test_path_traversal.sh \
|
||||||
|
tests/test_pqbox.sh \
|
||||||
|
tests/test_sdk.sh \
|
||||||
|
tests/test_source_only.sh
|
||||||
|
|
||||||
|
- name: Credential material audit (paths only)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
findings=$(git grep -Il -E -- \
|
||||||
|
"-----BEGIN (RSA |OPENSSH |EC |DSA )?PRIVATE KEY-----|https?://[^/@[:space:]]+:[A-Za-z0-9_+=.-]{20,}@|gh[pousr]_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{20,}|AKIA[A-Z0-9]{16}|xox[baprs]-[A-Za-z0-9-]{20,}|(FORGEJO_TOKEN|SECURITYOPS_TOKEN|GITHUB_TOKEN|CODEBERG_TOKEN)[[:space:]]*[:=][[:space:]]*['\\\"]?[A-Za-z0-9_+=./-]{20,}" \
|
||||||
|
-- . || true)
|
||||||
|
if [[ -n $findings ]]; then
|
||||||
|
printf '%s\n' "$findings" >&2
|
||||||
|
echo 'credential-like material found in tracked files' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo 'No private-key block, named token assignment, or credential-bearing URL found.'
|
||||||
|
|
||||||
build-and-test:
|
build-and-test:
|
||||||
|
name: Build and full tests (${{ matrix.cc }})
|
||||||
|
needs: source-policy
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
cc: [gcc, clang]
|
cc: [gcc, clang]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Install build deps
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: false
|
||||||
|
submodules: false
|
||||||
|
- name: Install build tools
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential clang dpkg-dev python3
|
sudo apt-get install -y build-essential clang file libarchive-tools python3 unzip
|
||||||
- name: Build (${{ matrix.cc }})
|
- name: Clean source-only build
|
||||||
run: make CC=${{ matrix.cc }} -j$(nproc)
|
run: |
|
||||||
- name: zupt version
|
make clean
|
||||||
run: ./zupt version
|
make -j"$(nproc)" CC=${{ matrix.cc }} V=1 WITH_SDK=0 WITH_PQBOX=0
|
||||||
- name: Full regression suite
|
- name: Distribution checks
|
||||||
run: make test
|
run: make CC=${{ matrix.cc }} V=1 WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
- name: License header audit
|
- name: Extended upstream tests
|
||||||
run: make audit-licenses
|
run: make CC=${{ matrix.cc }} V=1 WITH_SDK=0 WITH_PQBOX=0 test-all
|
||||||
|
- name: Functional test of the built CLI
|
||||||
|
run: bash scripts/test-installed-zupt.sh "$PWD/zupt"
|
||||||
|
|
||||||
# ─── Strict warning matrix — what the project's §6 protocol uses ───
|
|
||||||
strict-warnings:
|
strict-warnings:
|
||||||
|
name: Strict warnings (${{ matrix.cc }})
|
||||||
|
needs: source-policy
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- cc: gcc
|
- cc: gcc
|
||||||
cflags: "-Wall -Wextra -Wpedantic -Wshadow -Wcast-align -Wstrict-prototypes -Wmissing-prototypes -Wnull-dereference -Wformat-security -Wlogical-op -Wjump-misses-init -Wdouble-promotion -O2 -std=c11 -Werror"
|
flags: >-
|
||||||
|
-O2 -g -std=c11 -Wall -Wextra -Wpedantic -Wshadow
|
||||||
|
-Wcast-align -Wstrict-prototypes -Wmissing-prototypes
|
||||||
|
-Wnull-dereference -Wformat=2 -Werror
|
||||||
- cc: clang
|
- cc: clang
|
||||||
cflags: "-Wall -Wextra -Wpedantic -Wshadow -Wcast-align -Wstrict-prototypes -Wmissing-prototypes -Wnull-dereference -O2 -std=c11 -Werror"
|
flags: >-
|
||||||
|
-O2 -g -std=c11 -Wall -Wextra -Wpedantic -Wshadow
|
||||||
|
-Wcast-align -Wstrict-prototypes -Wmissing-prototypes
|
||||||
|
-Wnull-dereference -Wformat=2 -Werror
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Install build deps
|
|
||||||
run: sudo apt-get update && sudo apt-get install -y build-essential clang
|
|
||||||
- name: Strict ${{ matrix.cc }} build (warnings → errors)
|
|
||||||
run: make CC=${{ matrix.cc }} CFLAGS="${{ matrix.cflags }}" -j$(nproc)
|
|
||||||
|
|
||||||
# ─── ASAN + UBSAN — catches memory bugs the warning matrix can't ───
|
|
||||||
sanitizers:
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Install build deps
|
|
||||||
run: sudo apt-get update && sudo apt-get install -y build-essential python3
|
|
||||||
- name: Build with ASAN + UBSAN
|
|
||||||
run: make test-asan
|
|
||||||
- name: Native --pq byte-exact roundtrip under ASAN
|
|
||||||
env:
|
|
||||||
ASAN_OPTIONS: detect_leaks=0:abort_on_error=1
|
|
||||||
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
|
|
||||||
run: |
|
|
||||||
# Native hybrid ML-KEM-768 + X25519 (the source-only default; --pq-sdk
|
|
||||||
# needs a WITH_SDK=1 build and is unavailable here).
|
|
||||||
./zupt_asan keygen -o /tmp/k.priv
|
|
||||||
./zupt_asan keygen --pub -o /tmp/k.pub -k /tmp/k.priv
|
|
||||||
./zupt_asan compress --pq /tmp/k.pub /tmp/a.zupt include/
|
|
||||||
mkdir -p /tmp/extracted
|
|
||||||
./zupt_asan extract --pq /tmp/k.priv -o /tmp/extracted /tmp/a.zupt
|
|
||||||
diff -qr include /tmp/extracted/include
|
|
||||||
|
|
||||||
# ─── PIE hardening build — verifies no runtime breakage from -fPIE ───
|
|
||||||
pie-hardening:
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Install build deps
|
|
||||||
run: sudo apt-get update && sudo apt-get install -y build-essential
|
|
||||||
- name: Build with PIE + hardening
|
|
||||||
run: |
|
|
||||||
make CFLAGS="-O2 -std=c11 -fPIE -fstack-protector-strong -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security" \
|
|
||||||
LDFLAGS="-pie -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack" \
|
|
||||||
-j$(nproc)
|
|
||||||
- name: Verify binary is PIE
|
|
||||||
run: |
|
|
||||||
file ./zupt | grep -E "ELF .*executable.*pie|ELF .*shared object" || \
|
|
||||||
{ file ./zupt; echo "binary is not PIE"; exit 1; }
|
|
||||||
- name: Smoke test
|
|
||||||
run: |
|
|
||||||
echo "test" > /tmp/in.txt
|
|
||||||
./zupt c -p secret /tmp/a.zupt /tmp/in.txt
|
|
||||||
mkdir /tmp/out
|
|
||||||
(cd /tmp/out && ./../../home/runner/work/zupt/zupt/zupt x -p secret /tmp/a.zupt) || \
|
|
||||||
{ cd /tmp/out && "$GITHUB_WORKSPACE/zupt" x -p secret /tmp/a.zupt; }
|
|
||||||
diff -q /tmp/in.txt /tmp/out/in.txt
|
|
||||||
|
|
||||||
# ─── aarch64 cross-build via QEMU emulation ───
|
|
||||||
cross-aarch64:
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
with:
|
with:
|
||||||
platforms: arm64
|
persist-credentials: false
|
||||||
- name: Build + test inside aarch64 container
|
- name: Install compilers
|
||||||
run: |
|
|
||||||
docker run --rm --platform linux/arm64 \
|
|
||||||
-v "$PWD":/src -w /src \
|
|
||||||
ubuntu:24.04 \
|
|
||||||
bash -c '
|
|
||||||
apt-get update -qq
|
|
||||||
apt-get install -y -qq build-essential python3
|
|
||||||
make -j$(nproc)
|
|
||||||
./zupt version
|
|
||||||
make test
|
|
||||||
'
|
|
||||||
|
|
||||||
# ─── make dist reproducibility ───
|
|
||||||
dist-reproducibility:
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Install build deps
|
|
||||||
run: sudo apt-get update && sudo apt-get install -y build-essential python3
|
|
||||||
- name: First dist build
|
|
||||||
run: make dist
|
|
||||||
- name: Capture sha256 (run 1)
|
|
||||||
id: sha1
|
|
||||||
run: |
|
|
||||||
VER=$(grep '^#define ZUPT_VERSION_STRING' include/zupt.h | awk -F'"' '{print $2}')
|
|
||||||
SHA=$(sha256sum /tmp/vaptvupt-$VER.tar.gz | awk '{print $1}')
|
|
||||||
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "ver=$VER" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Run 1: $SHA"
|
|
||||||
- name: Second dist build (must produce identical sha256)
|
|
||||||
run: make dist
|
|
||||||
- name: Verify reproducibility
|
|
||||||
run: |
|
|
||||||
VER="${{ steps.sha1.outputs.ver }}"
|
|
||||||
SHA2=$(sha256sum /tmp/vaptvupt-$VER.tar.gz | awk '{print $1}')
|
|
||||||
if [ "$SHA2" != "${{ steps.sha1.outputs.sha }}" ]; then
|
|
||||||
echo "::error::make dist is NOT reproducible"
|
|
||||||
echo " run 1: ${{ steps.sha1.outputs.sha }}"
|
|
||||||
echo " run 2: $SHA2"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Reproducible ✓ ($SHA2)"
|
|
||||||
- name: Upload reproducible source tarball
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: zupt-source-tarball
|
|
||||||
path: /tmp/vaptvupt-*.tar.gz
|
|
||||||
|
|
||||||
# ─── Packaging-recipe syntax (cross-distro) ───
|
|
||||||
packaging-syntax:
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Install validators
|
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential dpkg-dev ruby rpm
|
sudo apt-get install -y build-essential clang
|
||||||
- name: Build (for include/zupt.h to exist; not strictly needed for syntax test)
|
- name: Compile with warnings as errors
|
||||||
run: make -j$(nproc)
|
run: |
|
||||||
- name: Run packaging syntax test
|
make clean
|
||||||
run: bash tests/test_packaging_syntax.sh
|
make -j"$(nproc)" CC=${{ matrix.cc }} V=1 WITH_SDK=0 WITH_PQBOX=0 \
|
||||||
|
CFLAGS="${{ matrix.flags }}"
|
||||||
|
|
||||||
# ─── Automatic GitHub release on git tag push ───
|
sanitizers:
|
||||||
release:
|
name: ASan, LSan and UBSan
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
needs: source-policy
|
||||||
needs: [build-and-test, strict-warnings, sanitizers, dist-reproducibility, packaging-syntax]
|
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Install build deps
|
with:
|
||||||
run: sudo apt-get update && sudo apt-get install -y build-essential python3
|
persist-credentials: false
|
||||||
- name: Build reproducible source tarball
|
- name: Install compiler and test tools
|
||||||
run: make dist
|
|
||||||
- name: Get version
|
|
||||||
id: ver
|
|
||||||
run: |
|
run: |
|
||||||
VER=$(grep '^#define ZUPT_VERSION_STRING' include/zupt.h | awk -F'"' '{print $2}')
|
sudo apt-get update
|
||||||
echo "version=$VER" >> "$GITHUB_OUTPUT"
|
sudo apt-get install -y build-essential file python3
|
||||||
- name: Verify tag matches version
|
- name: Instrumented functional tests
|
||||||
|
env:
|
||||||
|
ASAN_OPTIONS: detect_leaks=1:abort_on_error=1
|
||||||
|
UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1
|
||||||
|
run: make V=1 WITH_SDK=0 WITH_PQBOX=0 test-asan-run
|
||||||
|
- name: Mutation smoke under sanitizers
|
||||||
|
env:
|
||||||
|
ASAN_OPTIONS: detect_leaks=1:abort_on_error=1
|
||||||
|
UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1
|
||||||
|
run: make V=1 WITH_SDK=0 WITH_PQBOX=0 fuzz-format-run
|
||||||
|
|
||||||
|
static-analysis:
|
||||||
|
name: GCC static analyzer
|
||||||
|
needs: source-policy
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Install GCC
|
||||||
run: |
|
run: |
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
sudo apt-get update
|
||||||
EXPECTED="v${{ steps.ver.outputs.version }}"
|
sudo apt-get install -y build-essential
|
||||||
if [ "$TAG" != "$EXPECTED" ]; then
|
- name: Analyze every source translation unit
|
||||||
echo "::error::tag $TAG doesn't match include/zupt.h $EXPECTED"
|
run: |
|
||||||
|
make clean
|
||||||
|
make -j"$(nproc)" CC=gcc V=1 WITH_SDK=0 WITH_PQBOX=0 \
|
||||||
|
CFLAGS="-O1 -g -std=c11 -Wall -Wextra -Werror -fanalyzer"
|
||||||
|
|
||||||
|
source-archive:
|
||||||
|
name: Reproducible audited source archive
|
||||||
|
needs: source-policy
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: false
|
||||||
|
submodules: false
|
||||||
|
- name: Install archive audit tools
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y file libarchive-tools python3 unzip
|
||||||
|
- name: Build the source archive twice
|
||||||
|
run: |
|
||||||
|
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
|
mkdir -p "$RUNNER_TEMP/dist-one" "$RUNNER_TEMP/dist-two" \
|
||||||
|
"$RUNNER_TEMP/release-source"
|
||||||
|
make DIST_TARBALL="$RUNNER_TEMP/dist-one/zupt-$version.tar.gz" dist
|
||||||
|
make DIST_TARBALL="$RUNNER_TEMP/dist-two/zupt-$version.tar.gz" dist
|
||||||
|
cmp "$RUNNER_TEMP/dist-one/zupt-$version.tar.gz" \
|
||||||
|
"$RUNNER_TEMP/dist-two/zupt-$version.tar.gz"
|
||||||
|
cp "$RUNNER_TEMP/dist-one/zupt-$version.tar.gz" \
|
||||||
|
"$RUNNER_TEMP/release-source/"
|
||||||
|
(cd "$RUNNER_TEMP/release-source" && sha256sum "zupt-$version.tar.gz" > \
|
||||||
|
"zupt-$version.tar.gz.sha256")
|
||||||
|
bash scripts/check-source-only.sh --archive \
|
||||||
|
"$RUNNER_TEMP/release-source/zupt-$version.tar.gz"
|
||||||
|
- name: Match downstream recipe checksums to the tagged source archive
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
|
source_tar="$RUNNER_TEMP/release-source/zupt-$version.tar.gz"
|
||||||
|
actual_sha=$(sha256sum "$source_tar" | awk '{print $1}')
|
||||||
|
aur_sha=$(awk -F"'" '/^sha256sums=/ { print $2; exit }' packaging/aur/PKGBUILD)
|
||||||
|
homebrew_sha=$(awk -F'"' '/^[[:space:]]*sha256 / { print $2; exit }' packaging/homebrew/zupt.rb)
|
||||||
|
guix_base32=$(sed -n 's/^[[:space:]]*(base32 "\([^"]*\)").*/\1/p' \
|
||||||
|
packaging/guix/zupt.scm | head -n 1)
|
||||||
|
actual_base32=$(python3 - "$source_tar" <<'PY'
|
||||||
|
import hashlib
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
alphabet = "0123456789abcdfghijklmnpqrsvwxyz"
|
||||||
|
digest = hashlib.sha256(pathlib.Path(sys.argv[1]).read_bytes()).digest()
|
||||||
|
value = int.from_bytes(digest, "little")
|
||||||
|
length = (len(digest) * 8 + 4) // 5
|
||||||
|
print("".join(alphabet[(value >> (5 * index)) & 31]
|
||||||
|
for index in range(length - 1, -1, -1)))
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
[[ $aur_sha == "$actual_sha" && $homebrew_sha == "$actual_sha" ]] || {
|
||||||
|
echo 'AUR or Homebrew checksum does not match the source archive' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[[ $guix_base32 == "$actual_base32" ]] || {
|
||||||
|
echo 'Guix checksum does not match the source archive' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
- name: Upload source and checksum
|
||||||
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: release-source
|
||||||
|
path: ${{ runner.temp }}/release-source/*
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
debian-package:
|
||||||
|
name: Debian/Ubuntu source-built package
|
||||||
|
needs: [source-policy, build-and-test]
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Install Debian package tools
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y build-essential binutils dpkg-dev file git libarchive-tools python3 python3-pyqt6 unzip
|
||||||
|
- name: Build and extract-test the DEB
|
||||||
|
run: |
|
||||||
|
mkdir -p "$RUNNER_TEMP/release-deb"
|
||||||
|
DIST_DIR="$RUNNER_TEMP/release-deb" RUN_CHECKS=1 bash packaging/build-deb.sh
|
||||||
|
- name: Build and content-test the GUI DEB
|
||||||
|
run: |
|
||||||
|
mkdir -p "$RUNNER_TEMP/release-gui-deb"
|
||||||
|
DIST_DIR="$RUNNER_TEMP/release-gui-deb" bash packaging/build-gui-deb.sh
|
||||||
|
gui_deb="$RUNNER_TEMP/release-gui-deb/zupt-gui_5.2.2_all.deb"
|
||||||
|
test -s "$gui_deb"
|
||||||
|
test "$(dpkg-deb -f "$gui_deb" Package)" = zupt-gui
|
||||||
|
test "$(dpkg-deb -f "$gui_deb" Version)" = 5.2.2
|
||||||
|
test "$(dpkg-deb -f "$gui_deb" Architecture)" = all
|
||||||
|
- name: Install, functionally test and uninstall the DEBs
|
||||||
|
run: |
|
||||||
|
deb=$(find "$RUNNER_TEMP/release-deb" -maxdepth 1 -type f -name '*.deb' -print -quit)
|
||||||
|
gui_deb="$RUNNER_TEMP/release-gui-deb/zupt-gui_5.2.2_all.deb"
|
||||||
|
test -n "$deb" && test -s "$gui_deb"
|
||||||
|
sudo apt-get install -y "$deb" "$gui_deb"
|
||||||
|
bash scripts/test-installed-zupt.sh /usr/bin/zupt
|
||||||
|
QT_QPA_PLATFORM=offscreen zupt-gui --version | grep -Fx "zupt-gui 5.2.2"
|
||||||
|
test ! -e /usr/bin/vaptvupt
|
||||||
|
sudo apt-get purge -y zupt-gui zupt
|
||||||
|
test ! -e /usr/bin/zupt-gui
|
||||||
|
test ! -e /usr/bin/zupt
|
||||||
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: release-deb
|
||||||
|
path: ${{ runner.temp }}/release-deb/*.deb
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: release-gui-deb
|
||||||
|
path: ${{ runner.temp }}/release-gui-deb/*.deb
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
tumbleweed-rpm:
|
||||||
|
name: openSUSE Tumbleweed x86_64 RPM gate
|
||||||
|
needs: [source-policy, build-and-test]
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
container: opensuse/tumbleweed:latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
steps:
|
||||||
|
- name: Bootstrap Git before checkout
|
||||||
|
run: |
|
||||||
|
zypper --non-interactive --gpg-auto-import-keys refresh
|
||||||
|
zypper --non-interactive install --no-recommends \
|
||||||
|
bash ca-certificates git-core
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: false
|
||||||
|
submodules: false
|
||||||
|
- name: Install native openSUSE tooling
|
||||||
|
run: |
|
||||||
|
zypper --non-interactive install --no-recommends \
|
||||||
|
bash binutils cpio coreutils diffutils file findutils gawk gcc git-core grep gzip \
|
||||||
|
libxml2-tools make osc obs-service-obs_scm obs-service-recompress \
|
||||||
|
obs-service-tar python3-base rpm-build rpmlint sed \
|
||||||
|
shadow spec-cleaner tar unzip util-linux
|
||||||
|
- name: Confirm the Factory architecture gate
|
||||||
|
run: test "$(uname -m)" = x86_64
|
||||||
|
- name: Validate OBS service and spec syntax
|
||||||
|
run: |
|
||||||
|
xmllint --noout packaging/opensuse/_service
|
||||||
|
test -x /usr/lib/obs/service/obs_scm
|
||||||
|
test -x /usr/lib/obs/service/tar
|
||||||
|
test -x /usr/lib/obs/service/recompress
|
||||||
|
rpmspec -P packaging/opensuse/zupt.spec >/dev/null
|
||||||
|
spec-cleaner --no-copyright packaging/opensuse/zupt.spec \
|
||||||
|
> "$RUNNER_TEMP/zupt.spec.cleaned"
|
||||||
|
diff -u packaging/opensuse/zupt.spec \
|
||||||
|
"$RUNNER_TEMP/zupt.spec.cleaned"
|
||||||
|
- name: Exercise pinned OBS source service chain on release tags
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
run: |
|
||||||
|
service_dir=$RUNNER_TEMP/obs-service
|
||||||
|
mkdir -p "$service_dir"
|
||||||
|
cp packaging/opensuse/_service "$service_dir/"
|
||||||
|
# `osc service runall` additionally requires OBS working-copy metadata.
|
||||||
|
# Use osc's installed service executor to validate this standalone,
|
||||||
|
# repository-owned _service file with the exact same local services.
|
||||||
|
python3 - "$service_dir" <<'PY'
|
||||||
|
import sys
|
||||||
|
from xml.etree import ElementTree
|
||||||
|
from osc.obs_scm.serviceinfo import Serviceinfo
|
||||||
|
|
||||||
|
service_dir = sys.argv[1]
|
||||||
|
service_info = Serviceinfo()
|
||||||
|
service_info.read(ElementTree.parse(f"{service_dir}/_service").getroot())
|
||||||
|
raise SystemExit(service_info.execute(service_dir, "all", verbose=True))
|
||||||
|
PY
|
||||||
|
mapfile -t service_archives < <(find "$service_dir" -maxdepth 1 \
|
||||||
|
-type f -name 'zupt-*.tar.gz' -print)
|
||||||
|
test "${#service_archives[@]}" -eq 1
|
||||||
|
bash scripts/check-source-only.sh --archive "${service_archives[0]}"
|
||||||
|
- name: Build source and binary RPMs with real checks
|
||||||
|
run: |
|
||||||
|
mkdir -p "$RUNNER_TEMP/release-rpm"
|
||||||
|
DIST_DIR="$RUNNER_TEMP/release-rpm" bash packaging/build-rpm.sh
|
||||||
|
- name: Run rpmlint without suppressions
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
rpmlint "$RUNNER_TEMP"/release-rpm/*.rpm 2>&1 \
|
||||||
|
| tee "$RUNNER_TEMP/rpmlint.log"
|
||||||
|
if grep -Eq ': E:' "$RUNNER_TEMP/rpmlint.log"; then
|
||||||
|
echo 'rpmlint reported one or more errors' >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
- name: Compute sha256
|
- name: Install, functionally test and uninstall the RPM
|
||||||
id: sha
|
|
||||||
run: |
|
run: |
|
||||||
VER="${{ steps.ver.outputs.version }}"
|
rpm_file=$(find "$RUNNER_TEMP/release-rpm" -maxdepth 1 -type f \
|
||||||
SHA=$(sha256sum /tmp/vaptvupt-$VER.tar.gz | awk '{print $1}')
|
-name '*.rpm' ! -name '*.src.rpm' -print -quit)
|
||||||
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
|
test -n "$rpm_file"
|
||||||
echo "$SHA vaptvupt-$VER.tar.gz" > /tmp/vaptvupt-$VER.tar.gz.sha256
|
zypper --non-interactive install --allow-unsigned-rpm "$rpm_file"
|
||||||
- name: Create GitHub release
|
test_home=/tmp/zupt-ci-user
|
||||||
uses: softprops/action-gh-release@v2
|
useradd --create-home --home-dir "$test_home" --shell /bin/bash zupt-ci
|
||||||
|
runuser -u zupt-ci -- env HOME="$test_home" TMPDIR="$test_home" \
|
||||||
|
bash "$GITHUB_WORKSPACE/scripts/test-installed-zupt.sh" \
|
||||||
|
/usr/bin/zupt
|
||||||
|
test ! -e /usr/bin/vaptvupt
|
||||||
|
zypper --non-interactive remove zupt
|
||||||
|
test ! -e /usr/bin/zupt
|
||||||
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
files: |
|
name: release-rpm
|
||||||
/tmp/vaptvupt-${{ steps.ver.outputs.version }}.tar.gz
|
path: ${{ runner.temp }}/release-rpm/*.rpm
|
||||||
/tmp/vaptvupt-${{ steps.ver.outputs.version }}.tar.gz.sha256
|
if-no-files-found: error
|
||||||
body: |
|
retention-days: 7
|
||||||
## Zupt v${{ steps.ver.outputs.version }}
|
|
||||||
|
|
||||||
Reproducible source tarball.
|
gui-rpm-package:
|
||||||
|
name: Fedora noarch GUI RPM and SRPM gate
|
||||||
|
needs: [source-policy, build-and-test]
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
container: fedora:latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
steps:
|
||||||
|
- name: Bootstrap checkout dependencies
|
||||||
|
run: dnf install -y ca-certificates git
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: false
|
||||||
|
submodules: false
|
||||||
|
- name: Install native build, package, audit and GUI runtime tools
|
||||||
|
run: |
|
||||||
|
dnf install -y \
|
||||||
|
binutils cpio file findutils gcc git-core gzip libarchive make \
|
||||||
|
python3 python3-pyside6 rpm-build rpmdevtools tar unzip
|
||||||
|
- name: Build and content-test the GUI RPM and source RPM
|
||||||
|
run: |
|
||||||
|
mkdir -p "$RUNNER_TEMP/release-gui-rpm"
|
||||||
|
DIST_DIR="$RUNNER_TEMP/release-gui-rpm" bash packaging/build-gui-rpm.sh
|
||||||
|
test -s "$RUNNER_TEMP/release-gui-rpm/zupt-gui-5.2.2-1.noarch.rpm"
|
||||||
|
test -s "$RUNNER_TEMP/release-gui-rpm/zupt-gui-5.2.2-1.src.rpm"
|
||||||
|
test "$(rpm -qp --qf '%{NAME}' "$RUNNER_TEMP/release-gui-rpm/zupt-gui-5.2.2-1.noarch.rpm")" = zupt-gui
|
||||||
|
test "$(rpm -qp --qf '%{VERSION}-%{RELEASE}' "$RUNNER_TEMP/release-gui-rpm/zupt-gui-5.2.2-1.noarch.rpm")" = 5.2.2-1
|
||||||
|
test "$(rpm -qp --qf '%{ARCH}' "$RUNNER_TEMP/release-gui-rpm/zupt-gui-5.2.2-1.noarch.rpm")" = noarch
|
||||||
|
rpm -qp --requires "$RUNNER_TEMP/release-gui-rpm/zupt-gui-5.2.2-1.noarch.rpm" | \
|
||||||
|
grep -Fx 'zupt >= 5.2.2'
|
||||||
|
test "$(rpm -qp --qf '%{NAME}' "$RUNNER_TEMP/release-gui-rpm/zupt-gui-5.2.2-1.src.rpm")" = zupt-gui
|
||||||
|
- name: Build the matching Fedora CLI RPM
|
||||||
|
run: |
|
||||||
|
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
|
core_top="$RUNNER_TEMP/core-rpmbuild"
|
||||||
|
mkdir -p "$core_top"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
||||||
|
make DIST_TARBALL="$core_top/SOURCES/zupt-$version.tar.gz" dist
|
||||||
|
rpmbuild --define "_topdir $core_top" -ba packaging/rpm/zupt.spec
|
||||||
|
- name: Install and functionally test the GUI with the packaged CLI
|
||||||
|
run: |
|
||||||
|
core_rpm=$(find "$RUNNER_TEMP/core-rpmbuild/RPMS" -type f \
|
||||||
|
-name 'zupt-5.2.2-1.*.rpm' ! -name '*-debuginfo-*' \
|
||||||
|
! -name '*-debugsource-*' -print -quit)
|
||||||
|
gui_rpm="$RUNNER_TEMP/release-gui-rpm/zupt-gui-5.2.2-1.noarch.rpm"
|
||||||
|
test -n "$core_rpm" && test -s "$gui_rpm"
|
||||||
|
dnf install -y "$core_rpm" "$gui_rpm"
|
||||||
|
bash scripts/test-installed-zupt.sh /usr/bin/zupt
|
||||||
|
QT_QPA_PLATFORM=offscreen zupt-gui --version | grep -Fx 'zupt-gui 5.2.2'
|
||||||
|
test ! -e /usr/bin/vaptvupt
|
||||||
|
dnf remove -y zupt-gui zupt
|
||||||
|
test ! -e /usr/bin/zupt-gui
|
||||||
|
test ! -e /usr/bin/zupt
|
||||||
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: release-gui-rpm
|
||||||
|
path: ${{ runner.temp }}/release-gui-rpm/*.rpm
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
```
|
linux-portable:
|
||||||
sha256: ${{ steps.sha.outputs.sha }}
|
name: Linux x86_64 notice-bearing CLI tar.xz gate
|
||||||
```
|
needs: [source-policy, build-and-test]
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: false
|
||||||
|
submodules: false
|
||||||
|
- name: Install build and archive tools
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y build-essential binutils file python3 xz-utils
|
||||||
|
- name: Build and audit the native executable
|
||||||
|
run: |
|
||||||
|
test "$(uname -m)" = x86_64
|
||||||
|
make clean
|
||||||
|
make -j"$(nproc)" V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
|
||||||
|
make V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
|
||||||
|
bash scripts/test-installed-zupt.sh "$PWD/zupt"
|
||||||
|
if readelf -d zupt | grep -Eq '(RPATH|RUNPATH)'; then
|
||||||
|
echo 'Linux portable binary contains RPATH/RUNPATH' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mapfile -t needed < <(readelf -d zupt | sed -n 's/.*Shared library: \[\([^]]*\)\].*/\1/p')
|
||||||
|
((${#needed[@]} > 0))
|
||||||
|
for library in "${needed[@]}"; do
|
||||||
|
case $library in
|
||||||
|
libc.so.6|libm.so.6|libpthread.so.0) ;;
|
||||||
|
*) echo "unexpected Linux runtime dependency: $library" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
if ldd zupt | grep -Fq 'not found'; then
|
||||||
|
echo 'Linux portable binary has an unresolved runtime dependency' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- name: Assemble and extracted-package-test the tar.xz
|
||||||
|
run: |
|
||||||
|
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
|
epoch=$(<.source-date-epoch)
|
||||||
|
root="$RUNNER_TEMP/linux-work/zupt-$version-linux-x86_64"
|
||||||
|
output="$RUNNER_TEMP/release-linux-x86_64/zupt-$version-linux-x86_64.tar.xz"
|
||||||
|
mkdir -p "$root" "$(dirname "$output")"
|
||||||
|
install -m 0755 zupt "$root/zupt"
|
||||||
|
install -m 0644 README.md CHANGELOG.md SECURITY.md THREAT_MODEL.md \
|
||||||
|
LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-BSD-2-Clause \
|
||||||
|
LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE \
|
||||||
|
THIRD-PARTY-NOTICES.md "$root/"
|
||||||
|
tar --sort=name --mtime="@$epoch" --owner=0 --group=0 --numeric-owner \
|
||||||
|
-C "$(dirname "$root")" -cJf "$output" "$(basename "$root")"
|
||||||
|
extract=$(mktemp -d)
|
||||||
|
tar -xJf "$output" -C "$extract"
|
||||||
|
bash scripts/test-installed-zupt.sh \
|
||||||
|
"$extract/$(basename "$root")/zupt"
|
||||||
|
test "$(find "$extract/$(basename "$root")" -maxdepth 1 -type f | wc -l)" -eq 13
|
||||||
|
sha256sum "$output"
|
||||||
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: release-linux-x86_64
|
||||||
|
path: ${{ runner.temp }}/release-linux-x86_64/*.tar.xz
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
See CHANGELOG.md for release notes.
|
gui-portable:
|
||||||
|
name: Source-only GUI portable ZIP gate
|
||||||
|
needs: [source-policy, build-and-test]
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: false
|
||||||
|
submodules: false
|
||||||
|
- name: Install GUI smoke-test and archive tools
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y build-essential file python3 python3-pyqt6 unzip zip
|
||||||
|
- name: Assemble, audit and execute the portable GUI source bundle
|
||||||
|
run: |
|
||||||
|
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
|
make -j"$(nproc)" V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
|
||||||
|
root="$RUNNER_TEMP/gui-work/zupt-gui-$version-portable"
|
||||||
|
output="$RUNNER_TEMP/release-gui-portable/zupt-gui-$version-portable.zip"
|
||||||
|
mkdir -p "$root/assets" "$(dirname "$output")"
|
||||||
|
install -m 0644 gui/src/zupt_gui.py "$root/zupt_gui.py"
|
||||||
|
install -m 0755 packaging/portable/zupt-gui.sh \
|
||||||
|
packaging/portable/zupt-gui.command "$root/"
|
||||||
|
install -m 0644 packaging/portable/zupt-gui.bat "$root/"
|
||||||
|
install -m 0644 packaging/portable/README.txt "$root/README.txt"
|
||||||
|
install -m 0644 gui/assets/zupt-icon.png gui/assets/zupt.ico "$root/assets/"
|
||||||
|
install -m 0644 LICENSE-AGPL-3.0 gui/LICENSE-GUI CHANGELOG.md "$root/"
|
||||||
|
install -m 0644 gui/assets/README.md "$root/ASSET-PROVENANCE.md"
|
||||||
|
bash scripts/check-source-only.sh --tree "$root"
|
||||||
|
QT_QPA_PLATFORM=offscreen PATH="$PWD:$PATH" \
|
||||||
|
"$root/zupt-gui.sh" --version | grep -Fx "zupt-gui $version"
|
||||||
|
epoch=$(<.source-date-epoch)
|
||||||
|
find "$root" -exec touch -d "@$epoch" {} +
|
||||||
|
(cd "$(dirname "$root")" && zip -X -9 -r "$output" "$(basename "$root")")
|
||||||
|
extract=$(mktemp -d)
|
||||||
|
unzip -q "$output" -d "$extract"
|
||||||
|
bash scripts/check-source-only.sh --tree "$extract/$(basename "$root")"
|
||||||
|
QT_QPA_PLATFORM=offscreen PATH="$PWD:$PATH" \
|
||||||
|
"$extract/$(basename "$root")/zupt-gui.sh" --version | \
|
||||||
|
grep -Fx "zupt-gui $version"
|
||||||
|
sha256sum "$output"
|
||||||
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: release-gui-portable
|
||||||
|
path: ${{ runner.temp }}/release-gui-portable/*.zip
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
### Verifying the tarball
|
target-packages:
|
||||||
|
name: Windows and macOS release gates
|
||||||
```sh
|
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
|
||||||
sha256sum -c zupt-${{ steps.ver.outputs.version }}.tar.gz.sha256
|
needs:
|
||||||
```
|
- source-policy
|
||||||
|
- build-and-test
|
||||||
### Building
|
- strict-warnings
|
||||||
|
- sanitizers
|
||||||
```sh
|
- static-analysis
|
||||||
tar xzf zupt-${{ steps.ver.outputs.version }}.tar.gz
|
- source-archive
|
||||||
cd zupt-${{ steps.ver.outputs.version }}
|
- debian-package
|
||||||
make
|
- tumbleweed-rpm
|
||||||
make test
|
- gui-rpm-package
|
||||||
sudo make install
|
- linux-portable
|
||||||
```
|
- gui-portable
|
||||||
|
uses: ./.github/workflows/cross-platform.yml
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
|
||||||
379
.github/workflows/cross-platform.yml
vendored
379
.github/workflows/cross-platform.yml
vendored
|
|
@ -1,181 +1,252 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
#
|
|
||||||
# Cross-platform GUI + CLI binaries, built on REAL Windows and macOS runners.
|
|
||||||
#
|
|
||||||
# Why a dedicated workflow: the GUI is a PySide6/PyQt6 app and the CLI is
|
|
||||||
# portable C11, but self-contained native installers (Windows .exe/.msi,
|
|
||||||
# macOS .app/.dmg) can only be produced on the target OS. This workflow builds
|
|
||||||
# them on GitHub's windows-latest and macos-latest runners and attaches them to
|
|
||||||
# the GitHub release on a `v*` tag. Run it manually with "Run workflow"
|
|
||||||
# (workflow_dispatch) to smoke-test the build before tagging.
|
|
||||||
#
|
|
||||||
# Artifacts produced:
|
|
||||||
# Windows: vaptvupt.exe (CLI, mingw), vaptvupt-gui.exe (PyInstaller onefile),
|
|
||||||
# VaptVupt-Setup-<ver>.exe (Inno Setup installer)
|
|
||||||
# macOS: vaptvupt (CLI, universal where possible), VaptVupt-<ver>.dmg
|
|
||||||
# All: vaptvupt-gui-<ver>-portable.zip (Python GUI + launchers)
|
|
||||||
|
|
||||||
name: cross-platform
|
name: target release packages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_call:
|
||||||
tags: ['v*']
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ─────────────────────────── Windows ───────────────────────────
|
windows-x86_64:
|
||||||
windows:
|
name: Windows x86_64 package and smoke test
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: 'msys2 {0}'
|
shell: msys2 {0}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Check out the audited source
|
||||||
- name: Set up MSYS2 (mingw gcc + make)
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
uses: msys2/setup-msys2@v2
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: false
|
||||||
|
submodules: false
|
||||||
|
|
||||||
|
- name: Install the Windows C toolchain
|
||||||
|
uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0
|
||||||
with:
|
with:
|
||||||
msystem: UCRT64
|
msystem: UCRT64
|
||||||
update: true
|
update: true
|
||||||
install: >-
|
install: >-
|
||||||
|
mingw-w64-ucrt-x86_64-binutils
|
||||||
mingw-w64-ucrt-x86_64-gcc
|
mingw-w64-ucrt-x86_64-gcc
|
||||||
make
|
bsdtar
|
||||||
coreutils
|
coreutils
|
||||||
- name: Build CLI (vaptvupt.exe, source-only, C fallback crypto)
|
diffutils
|
||||||
run: |
|
file
|
||||||
make CC=gcc WITH_SDK=0 -j2
|
findutils
|
||||||
./vaptvupt.exe version || ./vaptvupt version
|
git
|
||||||
cp "$(ls vaptvupt.exe vaptvupt 2>/dev/null | head -1)" vaptvupt.exe 2>/dev/null || true
|
gzip
|
||||||
- name: Set up Python
|
make
|
||||||
shell: pwsh
|
python
|
||||||
run: |
|
tar
|
||||||
# Use the runner's native Python (not MSYS) for PyInstaller so the
|
unzip
|
||||||
# produced .exe targets the standard Windows Python ABI.
|
zip
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install PySide6 pyinstaller
|
|
||||||
- name: Get version
|
|
||||||
id: ver
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
$ver = (Select-String -Path include/zupt.h -Pattern '^#define ZUPT_VERSION_STRING "([^"]+)"').Matches.Groups[1].Value
|
|
||||||
"version=$ver" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
|
||||||
- name: Bundle GUI with PyInstaller (vaptvupt-gui.exe)
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
# onefile GUI that carries the CLI beside it via --add-binary.
|
|
||||||
pyinstaller --noconfirm --onefile --windowed `
|
|
||||||
--name vaptvupt-gui `
|
|
||||||
--icon gui/assets/zupt-icon.png `
|
|
||||||
--add-binary "vaptvupt.exe;." `
|
|
||||||
--add-data "gui/assets/zupt-icon.png;assets" `
|
|
||||||
gui/src/zupt_gui.py
|
|
||||||
- name: Build Inno Setup installer
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
choco install innosetup --no-progress -y
|
|
||||||
& "$env:ChocolateyInstall\bin\ISCC.exe" `
|
|
||||||
"/DAppVersion=${{ steps.ver.outputs.version }}" `
|
|
||||||
packaging/windows/vaptvupt-gui.iss
|
|
||||||
- name: Collect artifacts
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
$v = "${{ steps.ver.outputs.version }}"
|
|
||||||
New-Item -ItemType Directory -Force out | Out-Null
|
|
||||||
Copy-Item vaptvupt.exe "out/vaptvupt-$v-windows-x86_64.exe"
|
|
||||||
Copy-Item dist/vaptvupt-gui.exe "out/vaptvupt-gui-$v-windows-x86_64.exe"
|
|
||||||
if (Test-Path "packaging/windows/Output") {
|
|
||||||
Copy-Item packaging/windows/Output/*.exe "out/" -ErrorAction SilentlyContinue
|
|
||||||
}
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: windows
|
|
||||||
path: out/*
|
|
||||||
- name: Attach to release
|
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: out/*
|
|
||||||
|
|
||||||
# ─────────────────────────── macOS ───────────────────────────
|
- name: Audit source before building
|
||||||
macos:
|
run: bash scripts/check-source-only.sh
|
||||||
|
|
||||||
|
- name: Build from source
|
||||||
|
run: |
|
||||||
|
test "$(uname -m)" = x86_64
|
||||||
|
make clean
|
||||||
|
make -j2 CC=gcc V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
|
||||||
|
|
||||||
|
- name: Run the source-only distribution checks on Windows
|
||||||
|
run: make CC=gcc V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
|
||||||
|
|
||||||
|
- name: Native CLI smoke and round-trip
|
||||||
|
run: |
|
||||||
|
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
|
if [[ -x ./zupt.exe ]]; then
|
||||||
|
exe=$PWD/zupt.exe
|
||||||
|
elif [[ -x ./zupt ]]; then
|
||||||
|
exe=$PWD/zupt
|
||||||
|
else
|
||||||
|
echo 'ZUPT executable was not produced' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
version_output=$("$exe" --version)
|
||||||
|
version_line=${version_output%%$'\n'*}
|
||||||
|
read -r product reported_version _ <<< "$version_line"
|
||||||
|
if [[ $product != zupt || $reported_version != "$version" ]]; then
|
||||||
|
echo 'native Windows executable version does not match include/zupt.h' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
"$exe" --help >/dev/null
|
||||||
|
if "$exe" --definitely-invalid-option >/dev/null 2>&1; then
|
||||||
|
echo 'invalid option returned success' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
test_root=$(mktemp -d)
|
||||||
|
trap 'chmod -R u+rwX "$test_root" 2>/dev/null || true; rm -rf -- "$test_root"' EXIT
|
||||||
|
output_dir="$test_root/saída-安全"
|
||||||
|
archive="$test_root/cópia-安全.zupt"
|
||||||
|
mkdir -p "$test_root/input/subdir" "$output_dir"
|
||||||
|
printf 'Windows release smoke test\n' > "$test_root/input/café.txt"
|
||||||
|
printf 'UTF-8: café-安全\n' > "$test_root/input/subdir/ação-安全.txt"
|
||||||
|
printf 'emoji filename\n' > "$test_root/input/subdir/emoji-😀.bin"
|
||||||
|
: > "$test_root/input/empty"
|
||||||
|
dd if=/dev/urandom of="$test_root/input/subdir/random.bin" bs=4096 count=4 2>/dev/null
|
||||||
|
(cd "$test_root" && "$exe" compress "$archive" input)
|
||||||
|
"$exe" test "$archive"
|
||||||
|
"$exe" list "$archive" > "$test_root/list.txt" 2>&1
|
||||||
|
grep -F 'café.txt' "$test_root/list.txt"
|
||||||
|
grep -F 'ação-安全.txt' "$test_root/list.txt"
|
||||||
|
grep -F 'emoji-😀.bin' "$test_root/list.txt"
|
||||||
|
"$exe" extract -o "$output_dir" "$archive"
|
||||||
|
diff -r "$test_root/input" "$output_dir/input"
|
||||||
|
objdump -p "$exe" > "$test_root/imports.txt"
|
||||||
|
if grep -Eqi '(vendor[/\\]|libvuptsdk|libpqvaptvupt|libgcc_s|libstdc\+\+|libwinpthread|msys-2[.]0|cygwin1)[^[:space:]]*[.]dll' \
|
||||||
|
"$test_root/imports.txt"; then
|
||||||
|
echo 'Windows binary imports a non-system or vendored runtime' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
version_output=$(env PATH='/c/Windows/System32:/c/Windows' "$exe" --version)
|
||||||
|
version_line=${version_output%%$'\n'*}
|
||||||
|
read -r product reported_version _ <<< "$version_line"
|
||||||
|
if [[ $product != zupt || $reported_version != "$version" ]]; then
|
||||||
|
echo 'restricted-PATH Windows executable version does not match include/zupt.h' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Assemble Windows release files
|
||||||
|
run: |
|
||||||
|
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
|
bundle="out/work/zupt-$version-windows-x86_64"
|
||||||
|
mkdir -p out "$bundle"
|
||||||
|
if [[ -x ./zupt.exe ]]; then source_exe=./zupt.exe; else source_exe=./zupt; fi
|
||||||
|
install -m 0755 "$source_exe" "$bundle/zupt.exe"
|
||||||
|
install -m 0644 README.md CHANGELOG.md LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 \
|
||||||
|
LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 \
|
||||||
|
NOTICE THIRD-PARTY-NOTICES.md "$bundle/"
|
||||||
|
toolchain_prefix=${MINGW_PREFIX:-/ucrt64}
|
||||||
|
install -m 0644 \
|
||||||
|
"$toolchain_prefix/share/licenses/crt/COPYING" \
|
||||||
|
"$bundle/MINGW-CRT-COPYING.txt"
|
||||||
|
install -m 0644 \
|
||||||
|
"$toolchain_prefix/share/licenses/crt/COPYING.MinGW-w64-runtime.txt" \
|
||||||
|
"$bundle/COPYING.MinGW-w64-runtime.txt"
|
||||||
|
install -m 0644 \
|
||||||
|
"$toolchain_prefix/share/licenses/crt/COPYING.MinGW-w64.txt" \
|
||||||
|
"$bundle/COPYING.MinGW-w64.txt"
|
||||||
|
install -m 0644 \
|
||||||
|
"$toolchain_prefix/share/licenses/gcc-libs/COPYING3" \
|
||||||
|
"$bundle/GCC-COPYING3.txt"
|
||||||
|
install -m 0644 \
|
||||||
|
"$toolchain_prefix/share/licenses/gcc-libs/COPYING.RUNTIME" \
|
||||||
|
"$bundle/GCC-RUNTIME-LIBRARY-EXCEPTION.txt"
|
||||||
|
zip_path=$PWD/out/zupt-$version-windows-x86_64.zip
|
||||||
|
(cd out/work && zip -9 -r "$zip_path" \
|
||||||
|
"zupt-$version-windows-x86_64")
|
||||||
|
|
||||||
|
- name: Extract and functionally test the Windows ZIP
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
|
zip_path=$PWD/out/zupt-$version-windows-x86_64.zip
|
||||||
|
unzip -t "$zip_path"
|
||||||
|
extract_root=$(mktemp -d)
|
||||||
|
cleanup() {
|
||||||
|
chmod -R u+rwX "$extract_root" 2>/dev/null || true
|
||||||
|
rm -rf -- "$extract_root"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
unzip -q "$zip_path" -d "$extract_root"
|
||||||
|
for notice in MINGW-CRT-COPYING.txt COPYING.MinGW-w64-runtime.txt \
|
||||||
|
COPYING.MinGW-w64.txt GCC-COPYING3.txt \
|
||||||
|
GCC-RUNTIME-LIBRARY-EXCEPTION.txt; do
|
||||||
|
test -s "$extract_root/zupt-$version-windows-x86_64/$notice"
|
||||||
|
done
|
||||||
|
packaged_exe=$extract_root/zupt-$version-windows-x86_64/zupt.exe
|
||||||
|
test -x "$packaged_exe"
|
||||||
|
version_output=$(env PATH='/c/Windows/System32:/c/Windows' \
|
||||||
|
"$packaged_exe" --version)
|
||||||
|
version_line=${version_output%%$'\n'*}
|
||||||
|
read -r product reported_version _ <<< "$version_line"
|
||||||
|
if [[ $product != zupt || $reported_version != "$version" ]]; then
|
||||||
|
echo 'Windows ZIP executable version does not match include/zupt.h' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
env PATH='/c/Windows/System32:/c/Windows' "$packaged_exe" --help >/dev/null
|
||||||
|
mkdir -p "$extract_root/smoke/input" "$extract_root/smoke/saída-安全"
|
||||||
|
printf 'Windows ZIP package test\n' > "$extract_root/smoke/input/payload-ação-😀.txt"
|
||||||
|
(
|
||||||
|
cd "$extract_root/smoke"
|
||||||
|
env PATH='/c/Windows/System32:/c/Windows' \
|
||||||
|
"$packaged_exe" compress cópia-安全.zupt input
|
||||||
|
env PATH='/c/Windows/System32:/c/Windows' \
|
||||||
|
"$packaged_exe" test cópia-安全.zupt
|
||||||
|
env PATH='/c/Windows/System32:/c/Windows' \
|
||||||
|
"$packaged_exe" extract -o saída-安全 cópia-安全.zupt
|
||||||
|
)
|
||||||
|
cmp "$extract_root/smoke/input/payload-ação-😀.txt" \
|
||||||
|
"$extract_root/smoke/saída-安全/input/payload-ação-😀.txt"
|
||||||
|
|
||||||
|
- name: Upload tested Windows files
|
||||||
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: release-windows-x86_64
|
||||||
|
path: out/*.zip
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
macos-native:
|
||||||
|
name: macOS native DMG and installed-image test
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Check out the audited source
|
||||||
- name: Build CLI (vaptvupt, clang)
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
run: |
|
|
||||||
make CC=clang WITH_SDK=0 -j3
|
|
||||||
./vaptvupt version
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with: { python-version: '3.12' }
|
|
||||||
- name: Install GUI build deps
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install PySide6 pyinstaller
|
|
||||||
brew install create-dmg || true
|
|
||||||
- name: Get version
|
|
||||||
id: ver
|
|
||||||
run: echo "version=$(awk -F'\"' '/^#define ZUPT_VERSION_STRING/{print $2}' include/zupt.h)" >> "$GITHUB_OUTPUT"
|
|
||||||
- name: Bundle GUI (.app) with PyInstaller
|
|
||||||
run: |
|
|
||||||
pyinstaller --noconfirm --windowed \
|
|
||||||
--name "VaptVupt" \
|
|
||||||
--add-binary "vaptvupt:." \
|
|
||||||
--add-data "gui/assets/zupt-icon.png:assets" \
|
|
||||||
gui/src/zupt_gui.py
|
|
||||||
- name: Build .dmg
|
|
||||||
run: |
|
|
||||||
V="${{ steps.ver.outputs.version }}"
|
|
||||||
create-dmg --volname "VaptVupt $V" --window-size 500 300 \
|
|
||||||
--app-drop-link 350 120 --icon "VaptVupt.app" 150 120 \
|
|
||||||
"VaptVupt-$V.dmg" "dist/VaptVupt.app" || \
|
|
||||||
{ mkdir -p dmgroot && cp -R dist/VaptVupt.app dmgroot/ && \
|
|
||||||
hdiutil create -volname "VaptVupt $V" -srcfolder dmgroot -ov -format UDZO "VaptVupt-$V.dmg"; }
|
|
||||||
- name: Collect artifacts
|
|
||||||
run: |
|
|
||||||
V="${{ steps.ver.outputs.version }}"
|
|
||||||
mkdir -p out
|
|
||||||
cp vaptvupt "out/vaptvupt-$V-macos"
|
|
||||||
cp "VaptVupt-$V.dmg" out/
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
with:
|
||||||
name: macos
|
persist-credentials: false
|
||||||
path: out/*
|
fetch-depth: 0
|
||||||
- name: Attach to release
|
lfs: false
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
submodules: false
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: out/*
|
|
||||||
|
|
||||||
# ─────────────── Portable GUI (works on every OS) ───────────────
|
- name: Audit source before building
|
||||||
portable:
|
run: bash scripts/check-source-only.sh
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
- name: Build and validate the native DMG
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Get version
|
|
||||||
id: ver
|
|
||||||
run: echo "version=$(awk -F'\"' '/^#define ZUPT_VERSION_STRING/{print $2}' include/zupt.h)" >> "$GITHUB_OUTPUT"
|
|
||||||
- name: Assemble portable package
|
|
||||||
run: |
|
run: |
|
||||||
V="${{ steps.ver.outputs.version }}"
|
mkdir -p out
|
||||||
D="vaptvupt-gui-$V-portable"
|
DIST_DIR="$PWD/out" RUN_CHECKS=1 bash packaging/build-dmg.sh
|
||||||
mkdir -p "$D/assets"
|
|
||||||
cp gui/src/zupt_gui.py "$D/"
|
- name: Mount and functionally test the packaged binary
|
||||||
cp gui/assets/zupt-icon.png "$D/assets/"
|
run: |
|
||||||
cp packaging/portable/vaptvupt-gui.bat "$D/"
|
dmg=$(find out -maxdepth 1 -type f -name '*.dmg' -print -quit)
|
||||||
cp packaging/portable/vaptvupt-gui.command "$D/"
|
test -n "$dmg"
|
||||||
cp packaging/portable/vaptvupt-gui.sh "$D/"
|
mount_point=$(mktemp -d)
|
||||||
cp packaging/portable/README.txt "$D/"
|
cleanup() {
|
||||||
chmod +x "$D/vaptvupt-gui.command" "$D/vaptvupt-gui.sh"
|
hdiutil detach "$mount_point" >/dev/null 2>&1 || true
|
||||||
zip -r "$D.zip" "$D"
|
chmod -R u+rwX "$mount_point" 2>/dev/null || true
|
||||||
- uses: actions/upload-artifact@v4
|
rm -rf -- "$mount_point"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
hdiutil attach -nobrowse -readonly -mountpoint "$mount_point" "$dmg" >/dev/null
|
||||||
|
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' \
|
||||||
|
include/zupt.h)
|
||||||
|
packaged_binary=$mount_point/ZUPT.app/Contents/MacOS/zupt
|
||||||
|
version_output=$("$packaged_binary" --version)
|
||||||
|
version_line=${version_output%%$'\n'*}
|
||||||
|
read -r product reported_version _ <<< "$version_line"
|
||||||
|
if [[ $product != zupt || $reported_version != "$version" ]]; then
|
||||||
|
echo 'mounted macOS executable version does not match include/zupt.h' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
bash packaging/build-dmg.sh --test-binary \
|
||||||
|
"$packaged_binary"
|
||||||
|
hdiutil detach "$mount_point"
|
||||||
|
trap - EXIT HUP INT TERM
|
||||||
|
rmdir "$mount_point"
|
||||||
|
|
||||||
|
- name: Upload tested macOS DMG
|
||||||
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: portable
|
name: release-macos-native
|
||||||
path: vaptvupt-gui-*-portable.zip
|
path: out/*.dmg
|
||||||
- name: Attach to release
|
if-no-files-found: error
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
retention-days: 7
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: vaptvupt-gui-*-portable.zip
|
|
||||||
|
|
|
||||||
644
.github/workflows/promote-release.yml
vendored
Normal file
644
.github/workflows/promote-release.yml
vendored
Normal file
|
|
@ -0,0 +1,644 @@
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
|
|
||||||
|
name: Promote a tested release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
source_run_id:
|
||||||
|
description: Successful manually dispatched CI run that produced the assets
|
||||||
|
required: true
|
||||||
|
type: number
|
||||||
|
tag:
|
||||||
|
description: Existing annotated release tag, for example v5.2.2
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: promote-release-${{ inputs.tag }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
promote:
|
||||||
|
name: Promote tested assets to the canonical GitHub release
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
timeout-minutes: 45
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Validate the tag and source CI run through the GitHub API
|
||||||
|
id: provenance
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
SOURCE_RUN_ID: ${{ inputs.source_run_id }}
|
||||||
|
RELEASE_TAG: ${{ inputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
set +x
|
||||||
|
umask 077
|
||||||
|
[[ $SOURCE_RUN_ID =~ ^[1-9][0-9]*$ ]] || {
|
||||||
|
echo 'source_run_id must be a positive integer' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[[ $RELEASE_TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
|
||||||
|
echo 'tag must have the form vX.Y.Z' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
tag_ref_api="repos/$GITHUB_REPOSITORY/git/ref/tags/$RELEASE_TAG"
|
||||||
|
tag_object_type=$(gh api "$tag_ref_api" --jq '.object.type')
|
||||||
|
tag_object_sha=$(gh api "$tag_ref_api" --jq '.object.sha')
|
||||||
|
[[ $tag_object_type == tag && $tag_object_sha =~ ^[0-9a-f]{40}$ ]] || {
|
||||||
|
echo 'GitHub release ref is not an annotated tag' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
tag_object_api="repos/$GITHUB_REPOSITORY/git/tags/$tag_object_sha"
|
||||||
|
target_type=$(gh api "$tag_object_api" --jq '.object.type')
|
||||||
|
peeled_sha=$(gh api "$tag_object_api" --jq '.object.sha')
|
||||||
|
[[ $target_type == commit && $peeled_sha =~ ^[0-9a-f]{40}$ ]] || {
|
||||||
|
echo 'annotated tag does not point directly to a commit' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
run_api="repos/$GITHUB_REPOSITORY/actions/runs/$SOURCE_RUN_ID"
|
||||||
|
run_status=$(gh api "$run_api" --jq '.status')
|
||||||
|
run_conclusion=$(gh api "$run_api" --jq '.conclusion')
|
||||||
|
run_event=$(gh api "$run_api" --jq '.event')
|
||||||
|
run_head_branch=$(gh api "$run_api" --jq '.head_branch // ""')
|
||||||
|
run_workflow_id=$(gh api "$run_api" --jq '.workflow_id')
|
||||||
|
run_sha=$(gh api "$run_api" --jq '.head_sha')
|
||||||
|
run_repository=$(gh api "$run_api" --jq '.head_repository.full_name // ""')
|
||||||
|
workflow_path=$(gh api \
|
||||||
|
"repos/$GITHUB_REPOSITORY/actions/workflows/$run_workflow_id" \
|
||||||
|
--jq '.path')
|
||||||
|
[[ $run_status == completed && $run_conclusion == success ]] || {
|
||||||
|
echo 'source CI run is not completed successfully' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[[ $run_event == workflow_dispatch ]] || {
|
||||||
|
echo 'source CI run must have been started with workflow_dispatch' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[[ $run_head_branch == "$RELEASE_TAG" ]] || {
|
||||||
|
echo 'source CI run must have been dispatched from the release tag' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[[ $workflow_path == .github/workflows/ci.yml ]] || {
|
||||||
|
echo 'source run did not execute .github/workflows/ci.yml' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[[ $run_repository == "$GITHUB_REPOSITORY" ]] || {
|
||||||
|
echo 'source CI run belongs to a different head repository' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[[ $run_sha =~ ^[0-9a-f]{40}$ && $run_sha == "$peeled_sha" ]] || {
|
||||||
|
echo 'source CI head SHA does not match the peeled release tag' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
artifact_json=$RUNNER_TEMP/source-run-artifacts.json
|
||||||
|
gh api "$run_api/artifacts?per_page=100" > "$artifact_json"
|
||||||
|
python3 - "$artifact_json" <<'PY'
|
||||||
|
import json
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
payload = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||||
|
expected = {
|
||||||
|
"release-source",
|
||||||
|
"release-deb",
|
||||||
|
"release-rpm",
|
||||||
|
"release-gui-deb",
|
||||||
|
"release-gui-rpm",
|
||||||
|
"release-linux-x86_64",
|
||||||
|
"release-gui-portable",
|
||||||
|
"release-windows-x86_64",
|
||||||
|
"release-macos-native",
|
||||||
|
}
|
||||||
|
artifacts = payload.get("artifacts", [])
|
||||||
|
names = [artifact.get("name", "") for artifact in artifacts]
|
||||||
|
if payload.get("total_count") != len(expected):
|
||||||
|
raise SystemExit("source CI run artifact count mismatch")
|
||||||
|
if set(names) != expected or len(names) != len(set(names)):
|
||||||
|
raise SystemExit("source CI run artifact-name allowlist mismatch")
|
||||||
|
if any(artifact.get("expired") for artifact in artifacts):
|
||||||
|
raise SystemExit("one or more source CI artifacts have expired")
|
||||||
|
PY
|
||||||
|
{
|
||||||
|
printf 'head_sha=%s\n' "$peeled_sha"
|
||||||
|
printf 'tag_object_sha=%s\n' "$tag_object_sha"
|
||||||
|
printf 'tag=%s\n' "$RELEASE_TAG"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Check out the exact tested commit without persisted credentials
|
||||||
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
ref: ${{ steps.provenance.outputs.head_sha }}
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: false
|
||||||
|
submodules: false
|
||||||
|
|
||||||
|
- name: Confirm the local annotated tag and source version
|
||||||
|
id: release
|
||||||
|
env:
|
||||||
|
RELEASE_TAG: ${{ steps.provenance.outputs.tag }}
|
||||||
|
EXPECTED_SHA: ${{ steps.provenance.outputs.head_sha }}
|
||||||
|
EXPECTED_TAG_OBJECT: ${{ steps.provenance.outputs.tag_object_sha }}
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
[[ $(git rev-parse HEAD) == "$EXPECTED_SHA" ]] || {
|
||||||
|
echo 'checked-out commit differs from the validated source run' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[[ $(git cat-file -t "refs/tags/$RELEASE_TAG") == tag ]] || {
|
||||||
|
echo 'checked-out release ref is not an annotated tag' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[[ $(git rev-parse "refs/tags/$RELEASE_TAG") == "$EXPECTED_TAG_OBJECT" ]] || {
|
||||||
|
echo 'local annotated tag object differs from the validated GitHub tag' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
[[ $(git rev-parse "$RELEASE_TAG^{commit}") == "$EXPECTED_SHA" ]] || {
|
||||||
|
echo 'local peeled tag does not match the tested commit' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' \
|
||||||
|
include/zupt.h)
|
||||||
|
[[ -n $version && $RELEASE_TAG == "v$version" ]] || {
|
||||||
|
echo 'tag does not match include/zupt.h' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
printf 'version=%s\n' "$version" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Install validation tools
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y file libarchive-tools python3 python3-pyqt6 rpm unzip xz-utils
|
||||||
|
|
||||||
|
- name: Download the exact source artifact from the validated run
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: release-source
|
||||||
|
path: ${{ runner.temp }}/incoming/release-source
|
||||||
|
repository: ${{ github.repository }}
|
||||||
|
run-id: ${{ inputs.source_run_id }}
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Download the exact DEB artifact from the validated run
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: release-deb
|
||||||
|
path: ${{ runner.temp }}/incoming/release-deb
|
||||||
|
repository: ${{ github.repository }}
|
||||||
|
run-id: ${{ inputs.source_run_id }}
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Download the exact RPM artifact from the validated run
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: release-rpm
|
||||||
|
path: ${{ runner.temp }}/incoming/release-rpm
|
||||||
|
repository: ${{ github.repository }}
|
||||||
|
run-id: ${{ inputs.source_run_id }}
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Download the exact GUI DEB artifact from the validated run
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: release-gui-deb
|
||||||
|
path: ${{ runner.temp }}/incoming/release-gui-deb
|
||||||
|
repository: ${{ github.repository }}
|
||||||
|
run-id: ${{ inputs.source_run_id }}
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Download the exact GUI RPM artifacts from the validated run
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: release-gui-rpm
|
||||||
|
path: ${{ runner.temp }}/incoming/release-gui-rpm
|
||||||
|
repository: ${{ github.repository }}
|
||||||
|
run-id: ${{ inputs.source_run_id }}
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Download the exact Linux tar.xz artifact from the validated run
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: release-linux-x86_64
|
||||||
|
path: ${{ runner.temp }}/incoming/release-linux-x86_64
|
||||||
|
repository: ${{ github.repository }}
|
||||||
|
run-id: ${{ inputs.source_run_id }}
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Download the exact portable GUI source bundle
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: release-gui-portable
|
||||||
|
path: ${{ runner.temp }}/incoming/release-gui-portable
|
||||||
|
repository: ${{ github.repository }}
|
||||||
|
run-id: ${{ inputs.source_run_id }}
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Download the exact Windows artifact from the validated run
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: release-windows-x86_64
|
||||||
|
path: ${{ runner.temp }}/incoming/release-windows-x86_64
|
||||||
|
repository: ${{ github.repository }}
|
||||||
|
run-id: ${{ inputs.source_run_id }}
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Download the exact macOS artifact from the validated run
|
||||||
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||||
|
with:
|
||||||
|
name: release-macos-native
|
||||||
|
path: ${{ runner.temp }}/incoming/release-macos-native
|
||||||
|
repository: ${{ github.repository }}
|
||||||
|
run-id: ${{ inputs.source_run_id }}
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Enforce the allowlist and validate every release format
|
||||||
|
env:
|
||||||
|
RELEASE_TAG: ${{ steps.provenance.outputs.tag }}
|
||||||
|
VERSION: ${{ steps.release.outputs.version }}
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
umask 077
|
||||||
|
export LC_ALL=C
|
||||||
|
incoming=$RUNNER_TEMP/incoming
|
||||||
|
asset_dir=$RUNNER_TEMP/release-assets
|
||||||
|
mkdir -p "$asset_dir"
|
||||||
|
|
||||||
|
artifact_names=(
|
||||||
|
release-source
|
||||||
|
release-deb
|
||||||
|
release-rpm
|
||||||
|
release-gui-deb
|
||||||
|
release-gui-rpm
|
||||||
|
release-linux-x86_64
|
||||||
|
release-gui-portable
|
||||||
|
release-windows-x86_64
|
||||||
|
release-macos-native
|
||||||
|
)
|
||||||
|
expected_dirs=$RUNNER_TEMP/artifact-dirs.expected
|
||||||
|
actual_dirs=$RUNNER_TEMP/artifact-dirs.actual
|
||||||
|
printf '%s\0' "${artifact_names[@]}" | LC_ALL=C sort -z > "$expected_dirs"
|
||||||
|
find "$incoming" -mindepth 1 -maxdepth 1 -type d -printf '%f\0' | \
|
||||||
|
LC_ALL=C sort -z > "$actual_dirs"
|
||||||
|
cmp "$expected_dirs" "$actual_dirs" || {
|
||||||
|
echo 'downloaded artifact directory allowlist mismatch' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if find "$incoming" -mindepth 1 -maxdepth 1 ! -type d -print -quit | \
|
||||||
|
grep -q .; then
|
||||||
|
echo 'unexpected non-directory entry in artifact download root' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if find "$incoming" -mindepth 2 ! -type f -print -quit | grep -q .; then
|
||||||
|
echo 'artifact contains a directory, symlink, or special file' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
source_name="zupt-$VERSION.tar.gz"
|
||||||
|
source_sidecar="$source_name.sha256"
|
||||||
|
deb_name="zupt_${VERSION}_amd64.deb"
|
||||||
|
rpm_name="zupt-$VERSION-0.x86_64.rpm"
|
||||||
|
srpm_name="zupt-$VERSION-0.src.rpm"
|
||||||
|
gui_deb_name="zupt-gui_${VERSION}_all.deb"
|
||||||
|
gui_rpm_name="zupt-gui-$VERSION-1.noarch.rpm"
|
||||||
|
gui_srpm_name="zupt-gui-$VERSION-1.src.rpm"
|
||||||
|
linux_tar_name="zupt-$VERSION-linux-x86_64.tar.xz"
|
||||||
|
gui_portable_name="zupt-gui-$VERSION-portable.zip"
|
||||||
|
windows_zip_name="zupt-$VERSION-windows-x86_64.zip"
|
||||||
|
dmg_relative=()
|
||||||
|
for arch in x86_64 arm64; do
|
||||||
|
candidate="release-macos-native/ZUPT-$VERSION-macOS-$arch.dmg"
|
||||||
|
[[ ! -f $incoming/$candidate || -L $incoming/$candidate ]] || \
|
||||||
|
dmg_relative+=("$candidate")
|
||||||
|
done
|
||||||
|
((${#dmg_relative[@]} == 1)) || {
|
||||||
|
echo 'expected exactly one native macOS DMG' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_relative=(
|
||||||
|
"release-source/$source_name"
|
||||||
|
"release-source/$source_sidecar"
|
||||||
|
"release-deb/$deb_name"
|
||||||
|
"release-rpm/$rpm_name"
|
||||||
|
"release-rpm/$srpm_name"
|
||||||
|
"release-gui-deb/$gui_deb_name"
|
||||||
|
"release-gui-rpm/$gui_rpm_name"
|
||||||
|
"release-gui-rpm/$gui_srpm_name"
|
||||||
|
"release-linux-x86_64/$linux_tar_name"
|
||||||
|
"release-gui-portable/$gui_portable_name"
|
||||||
|
"release-windows-x86_64/$windows_zip_name"
|
||||||
|
"${dmg_relative[0]}"
|
||||||
|
)
|
||||||
|
expected_relative_list=$RUNNER_TEMP/artifact-files.expected
|
||||||
|
actual_relative_list=$RUNNER_TEMP/artifact-files.actual
|
||||||
|
printf '%s\0' "${expected_relative[@]}" | LC_ALL=C sort -z \
|
||||||
|
> "$expected_relative_list"
|
||||||
|
find "$incoming" -mindepth 2 -type f -printf '%P\0' | LC_ALL=C sort -z \
|
||||||
|
> "$actual_relative_list"
|
||||||
|
cmp "$expected_relative_list" "$actual_relative_list" || {
|
||||||
|
echo 'downloaded file allowlist mismatch' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_assets=()
|
||||||
|
for relative in "${expected_relative[@]}"; do
|
||||||
|
name=${relative#*/}
|
||||||
|
cp -- "$incoming/$relative" "$asset_dir/$name"
|
||||||
|
expected_assets+=("$name")
|
||||||
|
done
|
||||||
|
expected_list=$RUNNER_TEMP/release-assets.expected
|
||||||
|
printf '%s\0' "${expected_assets[@]}" | LC_ALL=C sort -z > "$expected_list"
|
||||||
|
|
||||||
|
source_tar=$asset_dir/$source_name
|
||||||
|
sidecar=$asset_dir/$source_sidecar
|
||||||
|
actual_source_sha=$(sha256sum "$source_tar" | awk '{print $1}')
|
||||||
|
[[ $(<"$sidecar") == "$actual_source_sha $source_name" ]] || {
|
||||||
|
echo 'source archive sidecar is not the exact expected SHA-256 record' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
(cd "$asset_dir" && sha256sum -c -- "$source_sidecar")
|
||||||
|
file "$source_tar" | grep -Eqi 'gzip compressed data'
|
||||||
|
tar -tzf "$source_tar" >/dev/null
|
||||||
|
bash scripts/check-source-only.sh --archive "$source_tar"
|
||||||
|
archive_version=$(tar -xOf "$source_tar" \
|
||||||
|
"zupt-$VERSION/include/zupt.h" | sed -n \
|
||||||
|
's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p')
|
||||||
|
[[ $archive_version == "$VERSION" && $RELEASE_TAG == "v$archive_version" ]] || {
|
||||||
|
echo 'source archive version does not match the release tag' >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
deb=$asset_dir/$deb_name
|
||||||
|
dpkg-deb --info "$deb" >/dev/null
|
||||||
|
[[ $(dpkg-deb -f "$deb" Package) == zupt ]]
|
||||||
|
[[ $(dpkg-deb -f "$deb" Version) == "$VERSION" ]]
|
||||||
|
[[ $(dpkg-deb -f "$deb" Architecture) == amd64 ]]
|
||||||
|
|
||||||
|
rpm_file=$asset_dir/$rpm_name
|
||||||
|
[[ $(rpm -qp --qf '%{NAME}' "$rpm_file") == zupt ]]
|
||||||
|
[[ $(rpm -qp --qf '%{VERSION}' "$rpm_file") == "$VERSION" ]]
|
||||||
|
[[ $(rpm -qp --qf '%{RELEASE}' "$rpm_file") == 0 ]]
|
||||||
|
[[ $(rpm -qp --qf '%{ARCH}' "$rpm_file") == x86_64 ]]
|
||||||
|
srpm=$asset_dir/$srpm_name
|
||||||
|
[[ $(rpm -qp --qf '%{NAME}' "$srpm") == zupt ]]
|
||||||
|
[[ $(rpm -qp --qf '%{VERSION}' "$srpm") == "$VERSION" ]]
|
||||||
|
[[ $(rpm -qp --qf '%{RELEASE}' "$srpm") == 0 ]]
|
||||||
|
[[ $(rpm -qp --qf '%{ARCH}' "$srpm") == src ]]
|
||||||
|
|
||||||
|
gui_deb=$asset_dir/$gui_deb_name
|
||||||
|
dpkg-deb --info "$gui_deb" >/dev/null
|
||||||
|
[[ $(dpkg-deb -f "$gui_deb" Package) == zupt-gui ]]
|
||||||
|
[[ $(dpkg-deb -f "$gui_deb" Version) == "$VERSION" ]]
|
||||||
|
[[ $(dpkg-deb -f "$gui_deb" Architecture) == all ]]
|
||||||
|
|
||||||
|
gui_rpm=$asset_dir/$gui_rpm_name
|
||||||
|
[[ $(rpm -qp --qf '%{NAME}' "$gui_rpm") == zupt-gui ]]
|
||||||
|
[[ $(rpm -qp --qf '%{VERSION}' "$gui_rpm") == "$VERSION" ]]
|
||||||
|
[[ $(rpm -qp --qf '%{RELEASE}' "$gui_rpm") == 1 ]]
|
||||||
|
[[ $(rpm -qp --qf '%{ARCH}' "$gui_rpm") == noarch ]]
|
||||||
|
rpm -qp --requires "$gui_rpm" | grep -Fx "zupt >= $VERSION"
|
||||||
|
gui_srpm=$asset_dir/$gui_srpm_name
|
||||||
|
[[ $(rpm -qp --qf '%{NAME}' "$gui_srpm") == zupt-gui ]]
|
||||||
|
[[ $(rpm -qp --qf '%{VERSION}' "$gui_srpm") == "$VERSION" ]]
|
||||||
|
[[ $(rpm -qp --qf '%{RELEASE}' "$gui_srpm") == 1 ]]
|
||||||
|
[[ $(rpm -qp --qf '%{ARCH}' "$gui_srpm") == src ]]
|
||||||
|
|
||||||
|
linux_tar=$asset_dir/$linux_tar_name
|
||||||
|
python3 - "$linux_tar" "zupt-$VERSION-linux-x86_64" <<'PY'
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
|
||||||
|
archive = pathlib.Path(sys.argv[1])
|
||||||
|
root = sys.argv[2]
|
||||||
|
expected_files = {
|
||||||
|
"zupt", "README.md", "CHANGELOG.md", "SECURITY.md",
|
||||||
|
"THREAT_MODEL.md", "LICENSE", "LICENSE-AGPL-3.0",
|
||||||
|
"LICENSE-GPL-3.0", "LICENSE-BSD-2-Clause",
|
||||||
|
"LICENSE-BSD-3-Clause", "LICENSE-CC0-1.0", "NOTICE",
|
||||||
|
"THIRD-PARTY-NOTICES.md",
|
||||||
|
}
|
||||||
|
with tarfile.open(archive, "r:xz") as package:
|
||||||
|
members = package.getmembers()
|
||||||
|
names = [member.name for member in members]
|
||||||
|
if len(names) != len(set(names)):
|
||||||
|
raise SystemExit("duplicate Linux tar member")
|
||||||
|
actual_files = set()
|
||||||
|
for member in members:
|
||||||
|
path = pathlib.PurePosixPath(member.name)
|
||||||
|
if (path.is_absolute() or ".." in path.parts or not path.parts or
|
||||||
|
path.parts[0] != root or member.issym() or member.islnk() or
|
||||||
|
not (member.isdir() or member.isfile())):
|
||||||
|
raise SystemExit("unsafe Linux tar member")
|
||||||
|
if member.isfile():
|
||||||
|
actual_files.add("/".join(path.parts[1:]))
|
||||||
|
if actual_files != expected_files:
|
||||||
|
raise SystemExit("Linux tar member allowlist mismatch")
|
||||||
|
PY
|
||||||
|
linux_extract=$RUNNER_TEMP/linux-package
|
||||||
|
mkdir -p "$linux_extract"
|
||||||
|
tar -xJf "$linux_tar" -C "$linux_extract"
|
||||||
|
linux_binary="$linux_extract/zupt-$VERSION-linux-x86_64/zupt"
|
||||||
|
file "$linux_binary" | grep -Eqi 'ELF.*executable'
|
||||||
|
bash scripts/test-installed-zupt.sh "$linux_binary"
|
||||||
|
|
||||||
|
gui_portable=$asset_dir/$gui_portable_name
|
||||||
|
python3 - "$gui_portable" "zupt-gui-$VERSION-portable" <<'PY'
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
archive = pathlib.Path(sys.argv[1])
|
||||||
|
root = sys.argv[2]
|
||||||
|
expected = {
|
||||||
|
f"{root}/", f"{root}/assets/", f"{root}/zupt_gui.py",
|
||||||
|
f"{root}/zupt-gui.sh", f"{root}/zupt-gui.command",
|
||||||
|
f"{root}/zupt-gui.bat", f"{root}/README.txt",
|
||||||
|
f"{root}/assets/zupt-icon.png", f"{root}/assets/zupt.ico",
|
||||||
|
f"{root}/LICENSE-AGPL-3.0", f"{root}/LICENSE-GUI",
|
||||||
|
f"{root}/ASSET-PROVENANCE.md", f"{root}/CHANGELOG.md",
|
||||||
|
}
|
||||||
|
with zipfile.ZipFile(archive) as package:
|
||||||
|
names = package.namelist()
|
||||||
|
if len(names) != len(set(names)) or set(names) != expected:
|
||||||
|
raise SystemExit("portable GUI ZIP member allowlist mismatch")
|
||||||
|
for name in names:
|
||||||
|
path = pathlib.PurePosixPath(name)
|
||||||
|
if path.is_absolute() or ".." in path.parts or path.parts[0] != root:
|
||||||
|
raise SystemExit("unsafe portable GUI ZIP member")
|
||||||
|
PY
|
||||||
|
bash scripts/check-source-only.sh --archive "$gui_portable"
|
||||||
|
gui_extract=$RUNNER_TEMP/gui-portable
|
||||||
|
mkdir -p "$gui_extract"
|
||||||
|
unzip -q "$gui_portable" -d "$gui_extract"
|
||||||
|
QT_QPA_PLATFORM=offscreen ZUPT_BIN="$linux_binary" \
|
||||||
|
"$gui_extract/zupt-gui-$VERSION-portable/zupt-gui.sh" --version | \
|
||||||
|
grep -Fx "zupt-gui $VERSION"
|
||||||
|
|
||||||
|
windows_zip=$asset_dir/$windows_zip_name
|
||||||
|
unzip -t "$windows_zip" >/dev/null
|
||||||
|
python3 - "$windows_zip" "zupt-$VERSION-windows-x86_64" <<'PY'
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
archive = pathlib.Path(sys.argv[1])
|
||||||
|
root = sys.argv[2]
|
||||||
|
with zipfile.ZipFile(archive) as package:
|
||||||
|
names = package.namelist()
|
||||||
|
if len(names) != len(set(names)):
|
||||||
|
raise SystemExit("duplicate Windows ZIP member")
|
||||||
|
expected = {
|
||||||
|
f"{root}/",
|
||||||
|
f"{root}/zupt.exe",
|
||||||
|
f"{root}/README.md",
|
||||||
|
f"{root}/CHANGELOG.md",
|
||||||
|
f"{root}/LICENSE",
|
||||||
|
f"{root}/LICENSE-AGPL-3.0",
|
||||||
|
f"{root}/LICENSE-GPL-3.0",
|
||||||
|
f"{root}/LICENSE-BSD-2-Clause",
|
||||||
|
f"{root}/LICENSE-BSD-3-Clause",
|
||||||
|
f"{root}/LICENSE-CC0-1.0",
|
||||||
|
f"{root}/NOTICE",
|
||||||
|
f"{root}/THIRD-PARTY-NOTICES.md",
|
||||||
|
f"{root}/MINGW-CRT-COPYING.txt",
|
||||||
|
f"{root}/COPYING.MinGW-w64-runtime.txt",
|
||||||
|
f"{root}/COPYING.MinGW-w64.txt",
|
||||||
|
f"{root}/GCC-COPYING3.txt",
|
||||||
|
f"{root}/GCC-RUNTIME-LIBRARY-EXCEPTION.txt",
|
||||||
|
}
|
||||||
|
if set(names) != expected:
|
||||||
|
raise SystemExit("Windows ZIP member allowlist mismatch")
|
||||||
|
for name in names:
|
||||||
|
path = pathlib.PurePosixPath(name)
|
||||||
|
if (path.is_absolute() or "\\" in name or ".." in path.parts or
|
||||||
|
not path.parts or path.parts[0] != root):
|
||||||
|
raise SystemExit("unsafe or unexpected Windows ZIP member")
|
||||||
|
executable = f"{root}/zupt.exe"
|
||||||
|
if names.count(executable) != 1:
|
||||||
|
raise SystemExit("Windows ZIP executable is missing or duplicated")
|
||||||
|
for notice in (
|
||||||
|
f"{root}/MINGW-CRT-COPYING.txt",
|
||||||
|
f"{root}/COPYING.MinGW-w64-runtime.txt",
|
||||||
|
f"{root}/COPYING.MinGW-w64.txt",
|
||||||
|
f"{root}/GCC-COPYING3.txt",
|
||||||
|
f"{root}/GCC-RUNTIME-LIBRARY-EXCEPTION.txt",
|
||||||
|
):
|
||||||
|
if not package.read(notice):
|
||||||
|
raise SystemExit("Windows toolchain notice is empty")
|
||||||
|
PY
|
||||||
|
unzip -p "$windows_zip" \
|
||||||
|
"zupt-$VERSION-windows-x86_64/zupt.exe" \
|
||||||
|
> "$RUNNER_TEMP/windows-zip-zupt.exe"
|
||||||
|
python3 - "$RUNNER_TEMP/windows-zip-zupt.exe" <<'PY'
|
||||||
|
import pathlib
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
executable = pathlib.Path(sys.argv[1])
|
||||||
|
with executable.open("rb") as stream:
|
||||||
|
header = stream.read(64)
|
||||||
|
if len(header) != 64 or header[:2] != b"MZ":
|
||||||
|
raise SystemExit("Windows ZIP executable lacks MZ magic")
|
||||||
|
pe_offset = struct.unpack_from("<I", header, 0x3C)[0]
|
||||||
|
stream.seek(pe_offset)
|
||||||
|
if stream.read(4) != b"PE\0\0":
|
||||||
|
raise SystemExit("Windows ZIP executable lacks PE signature")
|
||||||
|
PY
|
||||||
|
|
||||||
|
dmg_name=${dmg_relative[0]#*/}
|
||||||
|
dmg=$asset_dir/$dmg_name
|
||||||
|
python3 - "$dmg" <<'PY'
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
image = pathlib.Path(sys.argv[1])
|
||||||
|
with image.open("rb") as stream:
|
||||||
|
stream.seek(-512, 2)
|
||||||
|
if stream.read(4) != b"koly":
|
||||||
|
raise SystemExit("DMG lacks the UDIF trailer magic")
|
||||||
|
PY
|
||||||
|
|
||||||
|
checksum_tmp=$RUNNER_TEMP/SHA256SUMS
|
||||||
|
(cd "$asset_dir" && xargs -0 sha256sum < "$expected_list") \
|
||||||
|
> "$checksum_tmp"
|
||||||
|
mv "$checksum_tmp" "$asset_dir/SHA256SUMS"
|
||||||
|
(cd "$asset_dir" && sha256sum -c SHA256SUMS)
|
||||||
|
cp "$expected_list" "$RUNNER_TEMP/release-assets.list"
|
||||||
|
printf 'SHA256SUMS\0' >> "$RUNNER_TEMP/release-assets.list"
|
||||||
|
echo 'All downloaded release assets match the exact allowlist and formats.'
|
||||||
|
|
||||||
|
- name: Refuse to mutate an existing GitHub release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
RELEASE_TAG: ${{ steps.provenance.outputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
set +x
|
||||||
|
umask 077
|
||||||
|
existing_tags=$RUNNER_TEMP/github-release-tags
|
||||||
|
gh api --paginate "repos/$GITHUB_REPOSITORY/releases" \
|
||||||
|
--jq '.[].tag_name' > "$existing_tags"
|
||||||
|
if grep -Fxq -- "$RELEASE_TAG" "$existing_tags"; then
|
||||||
|
echo 'GitHub release already exists; refusing to replace or add assets' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Publish the already-tested byte-identical asset set
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
RELEASE_TAG: ${{ steps.provenance.outputs.tag }}
|
||||||
|
RELEASE_COMMIT: ${{ steps.provenance.outputs.head_sha }}
|
||||||
|
VERSION: ${{ steps.release.outputs.version }}
|
||||||
|
SOURCE_RUN_ID: ${{ inputs.source_run_id }}
|
||||||
|
run: |
|
||||||
|
set -Eeuo pipefail
|
||||||
|
set +x
|
||||||
|
umask 077
|
||||||
|
asset_dir=$RUNNER_TEMP/release-assets
|
||||||
|
(cd "$asset_dir" && sha256sum -c SHA256SUMS)
|
||||||
|
mapfile -d '' -t asset_names < "$RUNNER_TEMP/release-assets.list"
|
||||||
|
release_assets=()
|
||||||
|
for name in "${asset_names[@]}"; do
|
||||||
|
path=$asset_dir/$name
|
||||||
|
[[ -f $path && ! -L $path ]] || {
|
||||||
|
printf 'validated release asset disappeared or changed type: %q\n' \
|
||||||
|
"$name" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
release_assets+=("$path")
|
||||||
|
done
|
||||||
|
cat > "$RUNNER_TEMP/release-notes.md" <<EOF
|
||||||
|
ZUPT $VERSION was built and tested by manually dispatched CI run
|
||||||
|
https://github.com/$GITHUB_REPOSITORY/actions/runs/$SOURCE_RUN_ID
|
||||||
|
for annotated tag $RELEASE_TAG at commit $RELEASE_COMMIT. The job
|
||||||
|
definitions and logs in that run record the runner images,
|
||||||
|
architectures, toolchains, results, and explicit skips.
|
||||||
|
|
||||||
|
The attached source archive, CLI DEB/RPM/source RPM, GUI
|
||||||
|
DEB/RPM/source RPM, notice-bearing Linux x86_64 tar.xz, source-only
|
||||||
|
portable GUI ZIP, Windows CLI ZIP, and native macOS CLI DMG are the
|
||||||
|
exact artifacts validated by that run. SHA256SUMS records every
|
||||||
|
attached payload asset. GitHub is the canonical upstream release.
|
||||||
|
|
||||||
|
Binary packages are release-page assets only. The Git tree and source
|
||||||
|
archive remain source-only, built with WITH_SDK=0 and WITH_PQBOX=0.
|
||||||
|
AppImage and bare executables are intentionally excluded: the former
|
||||||
|
lacks an audited runtime source/relink handoff, while the latter does
|
||||||
|
not carry the required license and notice payload beside the program.
|
||||||
|
EOF
|
||||||
|
gh release create "$RELEASE_TAG" "${release_assets[@]}" \
|
||||||
|
--repo "$GITHUB_REPOSITORY" --draft --verify-tag \
|
||||||
|
--title "ZUPT $VERSION" \
|
||||||
|
--notes-file "$RUNNER_TEMP/release-notes.md"
|
||||||
|
gh release edit "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --draft=false
|
||||||
44
.gitignore
vendored
44
.gitignore
vendored
|
|
@ -1,39 +1,65 @@
|
||||||
# Build outputs
|
# Build outputs
|
||||||
/vaptvupt
|
|
||||||
/zupt
|
/zupt
|
||||||
|
# Optional compatibility symlink produced by legacy-alias builds.
|
||||||
|
/vaptvupt
|
||||||
*.o
|
*.o
|
||||||
|
*.obj
|
||||||
*.a
|
*.a
|
||||||
|
*.la
|
||||||
*.so
|
*.so
|
||||||
*.so.*
|
*.so.*
|
||||||
|
*.dll
|
||||||
*.dylib
|
*.dylib
|
||||||
*.exe
|
*.exe
|
||||||
*.obj
|
*.com
|
||||||
|
*.class
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.wasm
|
||||||
*.lib
|
*.lib
|
||||||
|
*.pdb
|
||||||
|
*.out
|
||||||
|
*.bin
|
||||||
|
*.elf
|
||||||
|
*.ko
|
||||||
|
*.mod
|
||||||
|
|
||||||
# Python bytecode caches
|
# Python bytecode caches
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
|
||||||
# Coverage / profiling
|
# Coverage / profiling
|
||||||
*.gcda
|
*.gcda
|
||||||
*.gcno
|
*.gcno
|
||||||
*.gcov
|
*.gcov
|
||||||
*.profraw
|
*.profraw
|
||||||
|
*.profdata
|
||||||
|
/coverage/
|
||||||
|
|
||||||
# CMake / out-of-tree build dirs
|
# CMake / out-of-tree build dirs
|
||||||
/build/
|
/build/
|
||||||
|
/dist/
|
||||||
|
/out/
|
||||||
|
/target/
|
||||||
|
/sdk/build/
|
||||||
/cmake-build-*/
|
/cmake-build-*/
|
||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
CMakeFiles/
|
CMakeFiles/
|
||||||
|
|
||||||
# Distribution tarballs and packages (published as release assets, not committed)
|
# Distribution tarballs and packages (published as release assets, not committed)
|
||||||
/*.tar.gz
|
*.tar.gz
|
||||||
/*.tar.xz
|
*.tar.xz
|
||||||
/*.zip
|
*.zip
|
||||||
/*.deb
|
*.deb
|
||||||
/*.rpm
|
*.rpm
|
||||||
/*.AppImage
|
*.AppImage
|
||||||
|
*.msi
|
||||||
|
*.apk
|
||||||
|
*.ipa
|
||||||
|
*.dmg
|
||||||
*.AppDir/
|
*.AppDir/
|
||||||
|
SHA256SUMS
|
||||||
SHA256SUMS.txt
|
SHA256SUMS.txt
|
||||||
|
|
||||||
# Test/scratch binaries
|
# Test/scratch binaries
|
||||||
|
|
@ -43,6 +69,8 @@ SHA256SUMS.txt
|
||||||
|
|
||||||
# Editor / OS noise
|
# Editor / OS noise
|
||||||
*.swp
|
*.swp
|
||||||
|
*.tmp
|
||||||
|
*.log
|
||||||
*~
|
*~
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.idea/
|
.idea/
|
||||||
|
|
|
||||||
1
.source-date-epoch
Normal file
1
.source-date-epoch
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
1788134400
|
||||||
253
AUDIT.md
253
AUDIT.md
|
|
@ -1,85 +1,194 @@
|
||||||
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
||||||
# VaptVupt — Security Audit
|
# ZUPT 5.2.2 audit guide and finding history
|
||||||
|
|
||||||
This document records the security review of VaptVupt: what is checked, how, the
|
This document describes review surfaces and reproducible checks. It is an
|
||||||
findings and their resolutions, and how to reproduce the checks. It complements
|
upstream self-review, not an independent audit, certification, or guarantee.
|
||||||
[SECURITY.md](SECURITY.md) (policy + primitives) and
|
`SECURITY.md` defines reporting policy and `THREAT_MODEL.md` defines the
|
||||||
[THREAT_MODEL.md](THREAT_MODEL.md) (what is and isn't defended).
|
security boundary.
|
||||||
|
|
||||||
Scope: the pure-C11 CLI (`src/`, `include/`) and the PySide6/PyQt6 GUI
|
## 5.2.2 scope
|
||||||
(`gui/src/zupt_gui.py`). Out of scope: the optional, separately distributed
|
|
||||||
`libzuptsdk` / `libpqvaptvupt` binaries (only present in a `make WITH_SDK=1`
|
|
||||||
build); the shipped source-only build contains no vendored binaries.
|
|
||||||
|
|
||||||
> **Not independently certified.** This is the project's own structured review,
|
The baseline scope is the source-only CLI and its bundled source codec:
|
||||||
> not a third-party accredited audit. Treat it as "reviewed, with reproducible
|
|
||||||
> evidence" and do your own review for high-assurance use.
|
|
||||||
|
|
||||||
## Methodology
|
- first-party C and headers under `src/` and `include/`;
|
||||||
|
- textual architecture-specific source under `jasmin/`, distinguishing
|
||||||
|
compiler-generated output from separately identified hand-written assembly;
|
||||||
|
- VaptVupt codec source at release 2.65.3, with provenance and licensing in
|
||||||
|
`THIRD-PARTY-NOTICES.md`;
|
||||||
|
- CLI tests, source scanner, build system, CI, and packaging recipes;
|
||||||
|
- the Python GUI source as a caller of the CLI.
|
||||||
|
|
||||||
| Technique | What it covers | Where |
|
The baseline is built with `WITH_SDK=0 WITH_PQBOX=0`. The optional system
|
||||||
|---|---|---|
|
`libvuptsdk` and `libpqvaptvupt` implementations are outside this scope unless
|
||||||
| **Cryptographic conformance vs an independent reference** | ML-KEM-768 is validated byte-for-byte against **OpenSSL 3.5's FIPS 203 ML-KEM-768** — deterministic keygen `ek` equality plus shared-secret agreement in both cross-decapsulation directions. | `tests/test_mlkem_fips203.sh`, in `make check` |
|
their exact source packages and versions are added to an assessment. Assembly
|
||||||
| **NIST/RFC known-answer vectors** | SHA-256 (FIPS 180-4), SHA-3/SHAKE (FIPS 202), AES-256-CTR (SP 800-38A F.5.5/F.5.6), HMAC-SHA256 (RFC 4231), X25519 (RFC 7748), ML-KEM-768, PBKDF2. | `tests/test_vectors.c` |
|
under `jasmin/` is disabled by default and is a separate `WITH_JASMIN=1` build
|
||||||
| **Byte-level tamper sweep** | Every byte position of a representative archive is flipped and re-opened; zero silent-accepts required (F-09). | `tests/` byte-sweep |
|
choice on supported x86_64 compiler targets. Generated files must record their
|
||||||
| **Authenticated-encryption fuzzing** | HMAC / integrity-trailer fuzz over many trials (F-06, F-08). | `tests/` |
|
compiler provenance; hand-written files must not be represented as compiler
|
||||||
| **Constant-time measurement** | dudect-style Welch t-test on the MAC-tag compare and the ML-KEM FO implicit-rejection compare (the two decapsulation-oracle-sensitive paths). | `tests/test_ct_timing.*` |
|
output.
|
||||||
| **Memory-safety sanitizers** | ASan + UBSan builds; exact-size decode cases; crafted-input decode. | `make test-asan` |
|
|
||||||
| **Static analysis** | cppcheck (warning/style/performance) on the first-party sources; strict `-Wall -Wextra -Wpedantic -Werror` gcc + clang matrix. | CI |
|
|
||||||
| **Formal annotations** | Frama-C/ACSL contracts on memory-safety-critical functions; 5 Jasmin-verified constant-time assembly routines (x86_64). | `include/zupt_acsl.h`, `jasmin/` |
|
|
||||||
| **Adversarial multi-agent review** | Independent reviewers per dimension (crypto, parser/memory-safety, CLI, GUI↔CLI contract, packaging), each finding then adversarially refuted before it is accepted. | manual, per release |
|
|
||||||
|
|
||||||
## Cryptographic conformance
|
## Source-only review
|
||||||
|
|
||||||
- **ML-KEM-768 — genuine FIPS 203 (v5.0.0).** Earlier releases shipped round-3
|
Release 5.2.2 removes incomplete SDK/PQBOX header snapshots and build
|
||||||
CRYSTALS-Kyber under a "FIPS 203" label; it was self-consistent and secure as
|
expectations for a local precompiled library. Git and new upstream source
|
||||||
an IND-CCA2 KEM but **not interoperable** with a compliant ML-KEM. Validating
|
archives are intended to contain no compiled executable, object, shared/static
|
||||||
against OpenSSL 3.5 revealed three deviations — a transposed matrix-`Â`
|
library, distribution package, unsafe symlink, or unresolved Git LFS pointer.
|
||||||
sampling convention (in both K-PKE.KeyGen and K-PKE.Encrypt), the round-3 final
|
|
||||||
KDF, and the implicit-rejection domain. All three were fixed and the result is
|
|
||||||
now byte-for-byte interoperable with OpenSSL in both directions. A permanent
|
|
||||||
conformance test guards against regression. This changed the shared secret, so
|
|
||||||
it is a wire-breaking change for `--pq`/`--pq-only` archives (see CHANGELOG
|
|
||||||
5.0.0 BREAKING).
|
|
||||||
- **Hybrid is the flagship.** `--pq` combines ML-KEM-768 with X25519 through a
|
|
||||||
SHA3-512 combiner; the archive key is secure if **either** primitive holds —
|
|
||||||
the strongest real-world posture and the recommended default. `--pq-only`
|
|
||||||
offers pure ML-KEM-768 for single-primitive compliance mandates.
|
|
||||||
- **Envelope.** AES-256-CTR with a **fresh random 128-bit nonce per block**
|
|
||||||
(the dedup keystream-reuse bug is fixed and regression-tested), HMAC-SHA256
|
|
||||||
Encrypt-then-MAC verified before any decryption, and an archive-integrity
|
|
||||||
trailer over the header/footer.
|
|
||||||
|
|
||||||
## Notable findings and resolutions (recent)
|
Run the same scanner over each representation:
|
||||||
|
|
||||||
| Sev | Finding | Resolution |
|
|
||||||
|---|---|---|
|
|
||||||
| High | ML-KEM-768 not FIPS 203-conformant / not interoperable | Fixed (transpose + KDF); validated vs OpenSSL; permanent conformance test |
|
|
||||||
| High | `compress -p out.zupt f1 f2` overwrote an input file (data loss, exit 0) | Refuse to overwrite an existing non-`.zupt` output without `-y/--force`; self-overwrite guard |
|
|
||||||
| High | `compress out.zupt dir -p pw` wrote an **unencrypted** archive (exit 0) | Error on a misplaced option after the archive (`--` escape available) |
|
|
||||||
| Critical | AES-CTR keystream reuse across `--dedup` blocks (many-time-pad) | Fresh random per-block nonce; regression test |
|
|
||||||
| Medium | Heap OOB read in the AVX2 decoder fast path on crafted input | Bound the 2-/3-byte offset read like the scalar tail path |
|
|
||||||
| Medium | GUI defaulted to SDK modes absent from the source-only build (unusable) | Reworked to native `--pq`/`--pq-only`; SDK shown only when supported |
|
|
||||||
| Low | Hybrid-decrypt did not wipe secret buffers on key-read failure | Wipe on the error path (matches the pq-only path) |
|
|
||||||
| Low | Untruthful banner (Argon2id-default / `/zupt` URL) on source-only builds | Build-aware, accurate `version`/`help` output |
|
|
||||||
| Critical* | Packaging (`debian/rules`, `aur`, `nix`, `homebrew`, `opensuse`) would fail a source-only build | Removed vendored-lib/`AUDIT.md` steps, fixed URLs, added completions |
|
|
||||||
|
|
||||||
\* build-time failure, not a runtime security issue.
|
|
||||||
|
|
||||||
## Known limitations / non-goals
|
|
||||||
|
|
||||||
- No protection against a compromised endpoint, a weak password, or key
|
|
||||||
custody failures (see THREAT_MODEL.md).
|
|
||||||
- Metadata (total archive size, block count) is observable.
|
|
||||||
- The review is reproducible but not third-party certified.
|
|
||||||
|
|
||||||
## Reproducing
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make check # vectors, tamper sweep, FIPS 203 conformance, guards
|
# tracked files and working tree
|
||||||
make test-asan # ASan + UBSan
|
scripts/check-source-only.sh
|
||||||
bash tests/test_mlkem_fips203.sh # FIPS 203 interop vs OpenSSL (needs openssl 3.5+)
|
|
||||||
|
# committed Git tree or immutable tag
|
||||||
|
scripts/check-source-only.sh --tag HEAD
|
||||||
|
scripts/check-source-only.sh --tag v5.2.2
|
||||||
|
|
||||||
|
# generated source archive
|
||||||
|
scripts/check-source-only.sh --archive /path/to/zupt-5.2.2.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
FIPS 203 conformance needs an ML-KEM-capable OpenSSL (3.5+); the test skips
|
The scanner checks extensions and magic bytes, nested archives, symlink targets,
|
||||||
gracefully otherwise (e.g. inside a distro package build).
|
LFS pointers, generated compiler output, and stale vendor-library references.
|
||||||
|
It reports paths without printing file contents. Its negative tests include
|
||||||
|
renamed ELF, ar, PE/MZ, versioned `.so`, RPM/DEB/AppImage, escaping symlinks,
|
||||||
|
and LFS pointers; textual assembly is a permitted source type.
|
||||||
|
|
||||||
|
Archive inspection must also fail closed at bounded recursion depth, member
|
||||||
|
count, individual expanded size, and total expanded size so a nested archive or
|
||||||
|
decompression bomb cannot turn the release scanner into an unbounded resource
|
||||||
|
consumer. This hardening and its adversarial fixtures are release-blocking and
|
||||||
|
remain `PENDING` until rerun on the exact candidate.
|
||||||
|
|
||||||
|
An unknown `.bin` fails by default. A necessary binary data fixture can be
|
||||||
|
declared only through `--data-manifest`, with four tab-separated fields for
|
||||||
|
path, purpose, provenance, and SPDX license. That manifest does not override a
|
||||||
|
compiled/executable magic finding.
|
||||||
|
|
||||||
|
An artifact is not clean merely because it has a harmless extension. Conversely,
|
||||||
|
binary image data is not executable code: the documented GUI icon assets are
|
||||||
|
necessary data and are reviewed separately for purpose, provenance, and license.
|
||||||
|
|
||||||
|
## Reproducible project checks
|
||||||
|
|
||||||
|
The baseline gates are:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make clean
|
||||||
|
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 1)" \
|
||||||
|
WITH_SDK=0 WITH_PQBOX=0 V=1
|
||||||
|
make WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
|
make WITH_SDK=0 WITH_PQBOX=0 test-all
|
||||||
|
```
|
||||||
|
|
||||||
|
Relevant review layers include:
|
||||||
|
|
||||||
|
| Layer | Evidence source | Interpretation |
|
||||||
|
|---|---|---|
|
||||||
|
| Source boundary | `scripts/check-source-only.sh`, `tests/test_source_only.sh` | Fails on prohibited artifacts or unsafe source layout |
|
||||||
|
| Primitive vectors | `tests/test_vectors.c` | Known-answer regression for implemented primitives |
|
||||||
|
| ML-KEM interoperability | `tests/test_mlkem_fips203.sh` | Runs only with an ML-KEM-capable OpenSSL 3.5+; otherwise `SKIP` |
|
||||||
|
| Archive behavior | quick/regression, traversal, argument-order, block-swap, nonce, and exact-size tests | Exercises current parser, integrity, and round-trip properties |
|
||||||
|
| Password sources | `tests/test_password_sources.sh` | Exercises password-file, inherited-descriptor and explicit-prompt rejection paths without logging password contents |
|
||||||
|
| Key files | native key regressions | Exercises no-replace private-file creation, POSIX mode `0600`/Windows current-user-only DACL, failed-partial behavior, checksum, and exact ZKEY/ZPQK version/flags/reserved/size/role validation |
|
||||||
|
| Terminal output | archive-comment regression | Requires displayed untrusted comments to contain no raw terminal-control sequence |
|
||||||
|
| Prompt cleanup | PTY signal regression | Requires handled POSIX interruption to restore the saved terminal state |
|
||||||
|
| Sanitizers | `make test-asan-run` | Builds and executes separate ASan/UBSan/LSan evidence where supported; not a substitute for normal tests |
|
||||||
|
| Static analysis | compiler analyzer, cppcheck, scan-build, clang-tidy where installed | Tool-specific findings must be reviewed, not suppressed globally |
|
||||||
|
| Shell and metadata | shellcheck, SPDX/license checks, packaging syntax checks | Applies only when the named tool actually executed |
|
||||||
|
| Source reproducibility | two `make dist` runs with identical committed input and epoch | Requires equal SHA-256 digests and clean archive scans |
|
||||||
|
| Installed package | target-native package inspection and `scripts/test-installed-zupt.sh` | Applies only to the exact OS/release/architecture tested |
|
||||||
|
|
||||||
|
This table identifies evidence, not results. The release validation report and
|
||||||
|
`packaging/opensuse/README.md` record `PASS`, `FAIL`, or `SKIP` for each actual
|
||||||
|
run. Missing tools, OBS access, other architectures, Leap, and SLE must not be
|
||||||
|
reported as passing without evidence.
|
||||||
|
|
||||||
|
The 5.2.2 key-file, terminal-comment, password-prompt, explicit-Bash regression,
|
||||||
|
and scanner-resource-limit changes were added during the final self-audit. All
|
||||||
|
affected checks and the complete required suite must be rerun after they land;
|
||||||
|
no earlier result is a final-candidate `PASS`.
|
||||||
|
|
||||||
|
## Cryptographic review boundary
|
||||||
|
|
||||||
|
The repository includes NIST/RFC known-answer tests and a conditional OpenSSL
|
||||||
|
ML-KEM interoperability check. These establish specific functional outputs in
|
||||||
|
the environments where they pass; they do not prove implementation security,
|
||||||
|
constant-time execution, or resistance to every malformed input.
|
||||||
|
|
||||||
|
Portable C is the default. Sensitive comparison/select code is written to avoid
|
||||||
|
secret-dependent branching, but compiler output remains platform-dependent.
|
||||||
|
Optional generated Jasmin functions have a narrower assurance scope and do not
|
||||||
|
formally verify the parser, codec, key management, or whole application. The C
|
||||||
|
AES implementation has documented cache-timing risk on hostile shared hardware.
|
||||||
|
|
||||||
|
## Historical findings
|
||||||
|
|
||||||
|
The following entries are retained as release history. Their regression tests
|
||||||
|
should be rerun, but the historical resolution does not itself constitute a
|
||||||
|
5.2.2 test result.
|
||||||
|
|
||||||
|
| First corrected | Severity | Finding | Resolution recorded at the time |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 4.2.0 | Critical | AES-CTR nonce reuse across encrypted `--dedup` blocks | Changed to fresh random per-block nonces; older affected archives should be re-encrypted |
|
||||||
|
| 5.0.0 | High | Native ML-KEM used round-3 CRYSTALS-Kyber semantics while labelled FIPS 203 | Corrected matrix/KDF/rejection behavior and added OpenSSL interoperability regression; native PQ compatibility changed |
|
||||||
|
| 5.0.0 | High | A malformed password-option ordering could overwrite an input | Added output/self-overwrite and argument-order guards |
|
||||||
|
| 5.0.0 | High | A misplaced option could cause plaintext output when encryption was intended | Reject misplaced options unless explicitly escaped |
|
||||||
|
| 5.0.0 | Medium | AVX2 codec offset read could exceed a crafted input tail | Added the scalar-equivalent bound and exact-size regression |
|
||||||
|
| 5.2.2 | Build/supply chain | Build and packaging paths expected local precompiled optional libraries | Removed incomplete vendor trees; optional integrations now require explicit system development packages |
|
||||||
|
| 5.2.2 | High | Extraction used mutable string paths and could follow hostile parent/leaf links or publish partially verified output | Added strict index-path validation, descriptor/handle-relative traversal, no-replace atomic publication, exact size/hash validation, and hostile-archive regressions |
|
||||||
|
| 5.2.2 | High | Compression or disk backup could name its own input through an alternate spelling, hardlink, or symlink | Compare open-file identity before creating the output in normal, solid, and disk-image writers; `--force` cannot bypass the guard |
|
||||||
|
| 5.2.2 | High | Disk restore validated a pathname before destructively consuming it and did not prove raw-device capacity before writing | Snapshot the measured archive privately before target open, restore from the same stream, and fail closed on unknown or insufficient device capacity |
|
||||||
|
| 5.2.2 | High | Some decoders did not require DATA at every payload position, and legacy encrypted+dedup disk references used a different AAD sequence | Enforce frame types in serial, threaded, solid, test, and disk readers; reconstruct the exact v5.2.1 disk AAD sequence with an actual encrypted DATA/DATA/REF/DATA fixture |
|
||||||
|
| 5.2.2 | Medium | Benchmark scratch paths were predictable from the process ID | Create one random private scratch directory and clean it without following links |
|
||||||
|
| 5.2.2 | High | Native private-key output and ZKEY/ZPQK readers did not uniformly enforce no-replace private permissions and every structural field | Create without replacement using POSIX mode `0600` or a Windows current-user-only DACL; validate checksum, version, flags, reserved bytes, exact size, and public/private role before use; leave a failed exclusive partial for manual removal rather than risk unlinking a pathname replacement |
|
||||||
|
| 5.2.2 | Medium | An authenticated archive comment could still inject terminal control sequences when displayed | Render untrusted comment bytes in a terminal-safe form without changing the authenticated archive value |
|
||||||
|
| 5.2.2 | Medium | A signal during an interactive POSIX password prompt could leave terminal echo/state altered | Restore saved terminal settings on handled interruptions; cover the behavior with a PTY regression |
|
||||||
|
| 5.2.2 | Test reliability | `tests/regression.sh` used Bash syntax without making the interpreter contract explicit | Execute the suite explicitly with Bash and keep syntax/interpreter checks in release gates |
|
||||||
|
| 5.2.2 | Documentation/licensing | Current documentation incorrectly denied historical MIT grants visible in published Git history | Added a factual erratum: current source follows current SPDX notices, while earlier grants and immutable tags remain valid and unmodified |
|
||||||
|
|
||||||
|
See `CHANGELOG.md` for the complete per-release history and compatibility notes.
|
||||||
|
Old tags remain immutable and may contain artifacts or build assumptions removed
|
||||||
|
from current branches and new tags.
|
||||||
|
|
||||||
|
## Packaging review
|
||||||
|
|
||||||
|
The openSUSE package must build the immutable source tag with
|
||||||
|
`WITH_SDK=0 WITH_PQBOX=0`, preserve distribution flags and debuginfo, run a real
|
||||||
|
`%check`, install through `DESTDIR`, and omit the renamed-era `vaptvupt` alias from
|
||||||
|
the main package. Review the built RPM for dependencies, paths, permissions,
|
||||||
|
RPATH/RUNPATH, hardening, debug information, licenses, and unowned files, then
|
||||||
|
test it after installation in a disposable target environment.
|
||||||
|
|
||||||
|
Release-page DEB, binary RPM, SRPM, notice-bearing Linux tar.xz, Windows ZIP,
|
||||||
|
and macOS DMG packages are separate outputs, never members of Git or source
|
||||||
|
archives. Publish only formats built and tested for their stated target and
|
||||||
|
include SHA-256 checksums. The gated GUI set adds the architecture-independent
|
||||||
|
DEB, noarch/source RPM, and source-only portable GUI ZIP. Package gates include
|
||||||
|
exact payload/dependency and installed off-screen integration checks; the
|
||||||
|
portable ZIP additionally receives source scans, an exact safe-member allowlist,
|
||||||
|
and an extracted launcher test. An AppImage is not promoted by the 5.2.2
|
||||||
|
policy; AppDir and Flatpak bundles, GUI platform installers, and bare
|
||||||
|
Linux/Windows executables are also excluded. Windows ZIP and macOS DMG outputs
|
||||||
|
remain CLI-only.
|
||||||
|
|
||||||
|
No Wine result is retained as release evidence for 5.2.2. Cross-compilation
|
||||||
|
does not establish native-Windows behavior. Extended-length/device namespace
|
||||||
|
paths, raw UNC output roots, and mapped/network-drive output are unsupported;
|
||||||
|
the native Windows workflow remains a publication gate for the ZIP containing
|
||||||
|
the executable.
|
||||||
|
|
||||||
|
## Known limitations
|
||||||
|
|
||||||
|
- No independent security audit or certification has been performed.
|
||||||
|
- Fuzzing and sanitizers sample behavior; they cannot prove absence of memory or
|
||||||
|
parser defects.
|
||||||
|
- Static analysis is tool-, configuration-, and path-dependent.
|
||||||
|
- Optional SDK/PQBOX code is outside the default assessment.
|
||||||
|
- Side-channel behavior is compiler-, CPU-, OS-, and workload-dependent.
|
||||||
|
- A clean source archive does not by itself establish that a binary was built
|
||||||
|
reproducibly or on a trusted runner.
|
||||||
|
- Passing on one OS or architecture is not evidence for another.
|
||||||
|
|
||||||
|
Record exact commands, tool versions, target, exit status, and non-sensitive
|
||||||
|
logs for every release gate. Never convert an unavailable or unexecuted check
|
||||||
|
from `SKIP` to `PASS`.
|
||||||
|
|
|
||||||
298
CHANGELOG.md
298
CHANGELOG.md
|
|
@ -1,4 +1,249 @@
|
||||||
# VaptVupt Changelog
|
# ZUPT Changelog
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.2] — 2026-08-31 — ZUPT identity, source-only upstream tree, and openSUSE packaging
|
||||||
|
|
||||||
|
This maintenance release keeps the `.zupt` extension, the v1.6 version byte,
|
||||||
|
and the bundled VaptVupt codec at 2.65.3. It adds flag-gated 5.2.2 encodings for
|
||||||
|
authenticated encrypted-dedup references and disk-image integrity/index
|
||||||
|
metadata. The 5.2.2 reader retains a narrow legacy v5.2.1 plain disk-index
|
||||||
|
path; older readers are not claimed to accept every archive written by 5.2.2.
|
||||||
|
|
||||||
|
### Product identity and compatibility
|
||||||
|
|
||||||
|
- Restored the original **ZUPT** product name and `zupt` command across the
|
||||||
|
application, GUI, packages, documentation, and release artifacts.
|
||||||
|
- Moved the canonical project location to
|
||||||
|
`https://github.com/cristiancmoises/zupt`.
|
||||||
|
- Kept the `.zupt` extension, format v1.6, `ZUPT` magic bytes, codec IDs,
|
||||||
|
`zupt_*`/`ZUPT_*` identifiers, and `zuptsdk_*` ABI unchanged. This is a
|
||||||
|
product-identity change, not an archive or cryptographic format change.
|
||||||
|
- Retained the name VaptVupt where it identifies the bundled codec, its
|
||||||
|
`vv_*` API/wire format, the `--vv`/`--vaptvupt` codec selector, or an external
|
||||||
|
compatibility contract. An optional `vaptvupt` command alias can support
|
||||||
|
scripts written for versions 3.0.0 through 5.2.1.
|
||||||
|
|
||||||
|
### Source and build
|
||||||
|
|
||||||
|
- Removed the incomplete `vendor/vuptsdk/` and `vendor/pqvaptvupt/` header
|
||||||
|
snapshots and every build expectation that a precompiled local `.so`, `.a`,
|
||||||
|
or `.o` is available. Git and newly generated source archives contain source
|
||||||
|
and necessary data only.
|
||||||
|
- `WITH_SDK` and `WITH_PQBOX` are disabled by default. When requested, they
|
||||||
|
resolve separately installed development libraries through `pkg-config` (or
|
||||||
|
explicit packager-supplied flags) and fail clearly when unavailable. The
|
||||||
|
build never downloads dependencies or falls back to a binary under `vendor/`.
|
||||||
|
- Reworked the Makefile to honor `CC`, `CPPFLAGS`, `CFLAGS`, `LDFLAGS`,
|
||||||
|
`LDLIBS`, `AR`, `RANLIB`, `STRIP`, `DESTDIR`, `PREFIX`, and install-directory
|
||||||
|
overrides. Architecture detection follows the compiler target. Baseline
|
||||||
|
builds no longer apply AVX2 to the whole program, add a private-library
|
||||||
|
RPATH, or strip distribution binaries.
|
||||||
|
- `make install DESTDIR=... PREFIX=/usr` supports staged package builds. The
|
||||||
|
`vaptvupt` command is an optional compatibility install, not part of the
|
||||||
|
openSUSE main package.
|
||||||
|
- Added `--password-prompt`, `--pass-file`, and `--pass-fd` to every CLI path
|
||||||
|
that accepts a password. These explicit sources avoid the historical
|
||||||
|
optional-argument ambiguity of `-p`; file/descriptor input rejects empty,
|
||||||
|
NUL-containing, and overlong values.
|
||||||
|
- Hardened native key handling: private outputs use no-replace creation with
|
||||||
|
POSIX mode `0600` or a Windows current-user-only DACL, and ZKEY/ZPQK inputs
|
||||||
|
must pass checksum, version, flags, reserved-byte, exact-size, and
|
||||||
|
public/private-role validation. A write/flush/close failure leaves its
|
||||||
|
incomplete or durability-uncertain exclusive file for manual review and
|
||||||
|
removal instead of risking an unlink-after-close race against a replacement
|
||||||
|
pathname.
|
||||||
|
- Added signal-aware POSIX password-prompt cleanup that restores saved terminal
|
||||||
|
state on handled interruption, with a PTY regression in the final gate.
|
||||||
|
- Corrected bundled-code provenance: pq-crystals/kyber-derived ML-KEM portions
|
||||||
|
now carry the upstream CC0-1.0 option and complete license text; the x86 BCJ
|
||||||
|
state machine is identified as an adaptation of Igor Pavlov's public-domain
|
||||||
|
LZMA SDK source instead of making an unsupported clean-room claim; and
|
||||||
|
curve25519-donna-derived X25519 portions retain the conservative upstream
|
||||||
|
BSD-3-Clause notice. The SHA-NI path now records its immutable
|
||||||
|
public-domain SHA-Intrinsics reference.
|
||||||
|
- Removed unsupported `JASMIN-VERIFIED` labels. The repository retains
|
||||||
|
Jasmin textual sources and generated/hand-written assembly plus runtime
|
||||||
|
tests, but no reproducible formal-proof certificate or log for these paths;
|
||||||
|
historical changelog claims below are qualified accordingly.
|
||||||
|
- Treat older “constant-time by construction” and formal-verification wording
|
||||||
|
as historical design claims unless a current reproducible proof artifact is
|
||||||
|
named. Source review and timing regressions do not prove the behavior of
|
||||||
|
every compiler, CPU, or final binary.
|
||||||
|
|
||||||
|
### Archive integrity and path security
|
||||||
|
|
||||||
|
- Reject archive entry names containing absolute roots, `.` or `..`
|
||||||
|
components, control characters, NTFS alternate-stream syntax, trailing
|
||||||
|
dot/space components, or reserved DOS device names. Empty, overlong, and
|
||||||
|
embedded-NUL index paths are rejected during parsing.
|
||||||
|
- Create output below a pinned destination. POSIX systems traverse parents with
|
||||||
|
`openat()`/`mkdirat()` and `O_NOFOLLOW` after resolving the user-selected root
|
||||||
|
to a physical path once; Windows traverses and creates each component with
|
||||||
|
handle-relative `NtCreateFile` and publishes with a handle-relative
|
||||||
|
`FileRenameInfo`. Neither implementation re-resolves a checked archive path
|
||||||
|
through a mutable parent.
|
||||||
|
- Write to a private, newly created temporary file and publish it atomically
|
||||||
|
only after the decoded size, per-file checksum, archive integrity checks, and
|
||||||
|
close/flush operations succeed. Existing regular files, hardlinks, symlinks,
|
||||||
|
or reparse points are never overwritten.
|
||||||
|
- Normal, solid, and disk-image compression now use the same private-temporary
|
||||||
|
discipline in the destination directory. Publication replaces only the
|
||||||
|
requested directory entry, so an output symlink or hardlink cannot truncate
|
||||||
|
its target; any input, encryption, or write failure preserves the previous
|
||||||
|
archive and removes the temporary. Disk-image indices now use the canonical
|
||||||
|
varint encoding and pass the normal `list` and `test` parsers. Disk backup
|
||||||
|
measures and reads the source through one open descriptor, rejects a size
|
||||||
|
change, and cannot be redirected by exchanging the source pathname. Before
|
||||||
|
creating the temporary output, normal compression and disk backup also reject
|
||||||
|
an output that identifies the same file as the input through an alternate
|
||||||
|
spelling, hardlink, or symlink; `--force` does not bypass this guard.
|
||||||
|
- Disk restore now copies the measured compacted archive into one private,
|
||||||
|
auto-deleted scratch file before opening the destructive destination. Its
|
||||||
|
preflight and restore phases consume that same snapshot, so exchanging the
|
||||||
|
source pathname cannot change the bytes after validation. `ZUPT_TMPDIR`
|
||||||
|
selects an existing scratch directory and fails without fallback when it is
|
||||||
|
invalid or lacks space. Linux, macOS, and FreeBSD raw block-device capacity
|
||||||
|
is queried before the first write; an unknown or undersized device fails
|
||||||
|
closed. Regular-file restore retains atomic publication.
|
||||||
|
- Bind every encrypted DATA frame to its logical file/block position (or disk
|
||||||
|
block position), including when deduplication is enabled. An authenticated
|
||||||
|
DEDUP_REF is bound to its own logical position and carries the authenticated
|
||||||
|
source position needed to verify the referenced DATA frame. Swapping either
|
||||||
|
kind of frame therefore fails authentication instead of relying on the former
|
||||||
|
archive-wide dedup AAD sentinel. Generic `test` and byte-exact `disk restore`
|
||||||
|
regressions cover encrypted `disk backup --dedup`; they remain part of the
|
||||||
|
exact final-candidate gate described below.
|
||||||
|
- Require both XXH64 and an independent SHA-256/128 digest match before
|
||||||
|
emitting a dedup reference, and authenticate reference offsets in new
|
||||||
|
encrypted archives.
|
||||||
|
- Require a valid archive-integrity trailer in the `extract`, `list`, `test`,
|
||||||
|
and `disk restore` paths by default, regardless of unauthenticated header
|
||||||
|
flags. `--allow-legacy-no-ait` is an explicit recovery-only override for
|
||||||
|
those commands when given a known, trusted pre-AIT archive; it emits a
|
||||||
|
downgrade warning. Writers never use the override or create a no-AIT archive.
|
||||||
|
`info` remains an unauthenticated framing inspection: it reports AIT presence
|
||||||
|
but does not validate the trailer or archive contents.
|
||||||
|
- Authenticate the encrypted disk index and record a chained whole-image XXH64
|
||||||
|
content hash in new disk archives. Generic `test` and `disk restore` verify
|
||||||
|
block count, restored size, reference targets, and that content hash. XXH64
|
||||||
|
remains a non-cryptographic corruption check in a plain archive.
|
||||||
|
- Retain compatibility parsers for the fixed little-endian disk index and the
|
||||||
|
linear encrypted-dedup AAD sequence published through 5.2.1. The regression
|
||||||
|
fixture is an actual v5.2.1 password-encrypted DATA/DATA/REF/DATA disk archive,
|
||||||
|
stored as hexadecimal text with its source tag/commit, password, input hash,
|
||||||
|
and archive hash. The 5.2.2 candidate lists, tests, extracts, and restores it byte-exact;
|
||||||
|
the legacy index has no whole-image hash and produces an explicit warning.
|
||||||
|
The exact final candidate must repeat this gate, and no broader historical
|
||||||
|
compatibility claim is made.
|
||||||
|
- Serialize fixed-width archive/header, footer, index and PBKDF iteration
|
||||||
|
fields explicitly in little-endian order. Varint readers now reject overlong
|
||||||
|
encodings and values wider than 64 bits instead of accepting an ambiguous or
|
||||||
|
wrapped scalar.
|
||||||
|
- Require a DATA frame wherever a decoder consumes file or disk payload. The
|
||||||
|
multithreaded and serial readers, solid reader, generic `test`, and disk
|
||||||
|
restore now reject type-confused frames rather than decoding COMMENT or INDEX
|
||||||
|
payload as ordinary data.
|
||||||
|
- Add structurally valid hostile-archive fixtures covering traversal, absolute
|
||||||
|
and Windows-special paths, destination leaf/ancestor links, a relative user
|
||||||
|
output root, corrupt payload cleanup, nested UTF-8 paths, and safe separator
|
||||||
|
normalization.
|
||||||
|
- Scope the Windows output boundary to normal local Win32 paths. Cross-build
|
||||||
|
and Wine results are not native-Windows evidence; the native package workflow,
|
||||||
|
including its Unicode round trip, remains a release gate.
|
||||||
|
Extended-length/device namespace paths, raw UNC output roots, and
|
||||||
|
mapped/network-drive output are unsupported in 5.2.2.
|
||||||
|
- Create benchmark corpora, archives, extraction outputs, and concatenation
|
||||||
|
inputs below a randomly generated private temporary directory and clean it
|
||||||
|
recursively without following links. This replaces the predictable
|
||||||
|
process-ID-only scratch path used by earlier builds.
|
||||||
|
- Render archive comments as untrusted terminal data: control bytes are shown
|
||||||
|
safely rather than emitted as raw terminal-control sequences. The stored and
|
||||||
|
authenticated comment bytes are unchanged.
|
||||||
|
|
||||||
|
### Licensing and provenance erratum
|
||||||
|
|
||||||
|
- Corrected prior documentation that incorrectly denied all historical MIT
|
||||||
|
grants. Commit `d4660e6539c8b6eeba81751c018217d978fdd618` distributed the
|
||||||
|
then-current first-party application and GUI with MIT license files, and the
|
||||||
|
immutable `v2.2.2` tag contains an MIT-form `gui/LICENSE-GUI` alongside an
|
||||||
|
AGPL SPDX notice in the GUI source. Those records are preserved and their
|
||||||
|
historical permissions are not revoked or reinterpreted by 5.2.2.
|
||||||
|
- Current source follows its current per-file SPDX notices: the application,
|
||||||
|
GUI, cryptographic tool, build, test, and documentation code are
|
||||||
|
AGPL-3.0-or-later, while the identified bundled codec source is
|
||||||
|
GPL-3.0-or-later. The erratum corrects the record; it does not rewrite old
|
||||||
|
tags or change the license of a historical copy.
|
||||||
|
- Distinguished Jasmin compiler output from hand-written textual assembly.
|
||||||
|
`zupt_aes_ctr4.s` is a hand-written production implementation corresponding
|
||||||
|
to an algorithm-only `.jazz` description; generated files retain their
|
||||||
|
available compiler provenance.
|
||||||
|
- Corrected the stale claim that the adapted XXH64 code was public domain.
|
||||||
|
Both derived implementations now preserve Yann Collet's BSD-2-Clause
|
||||||
|
copyright, conditions, disclaimer, and compound SPDX scope; binary package
|
||||||
|
metadata and license payloads include BSD-2-Clause.
|
||||||
|
|
||||||
|
### Audit, tests, packaging, and release
|
||||||
|
|
||||||
|
- Added a reusable source-only scanner for tracked files, the working tree,
|
||||||
|
`git archive`, release archives, nested archives, unsafe symlinks, Git LFS
|
||||||
|
pointers, compiler output, executable magic, and stale vendor-library
|
||||||
|
references. Positive and negative regression tests cover renamed ELF, ar,
|
||||||
|
PE/MZ, versioned `.so`, RPM/DEB/AppImage, escaping symlinks, and LFS pointers
|
||||||
|
while permitting textual assembly. Necessary non-code `.bin` data requires a
|
||||||
|
manifest entry with purpose, provenance, and SPDX license; compiled magic is
|
||||||
|
never allowlisted. Nested inspection now has fail-closed recursion, member,
|
||||||
|
per-entry expansion, and total-expansion limits with decompression-bomb
|
||||||
|
regressions.
|
||||||
|
- Make `tests/regression.sh`'s Bash interpreter requirement explicit so running
|
||||||
|
it through a non-Bash `/bin/sh` cannot masquerade as a product regression.
|
||||||
|
- Added upstream openSUSE/OBS packaging under `packaging/opensuse/`, built with
|
||||||
|
`WITH_SDK=0 WITH_PQBOX=0`, real `%check` execution, staged installation, and
|
||||||
|
no installed `vaptvupt` alias. The OBS service tracks the canonical ZUPT
|
||||||
|
repository and an immutable release tag; no acceptance by OBS or Factory is
|
||||||
|
implied by files being present upstream.
|
||||||
|
- Updated CI and release checks around clean source builds, tests, source
|
||||||
|
archive inspection, packaging metadata, licenses, and secret hygiene.
|
||||||
|
- The gated release path is defined to produce the audited source archive plus
|
||||||
|
an Ubuntu amd64 CLI DEB, openSUSE Tumbleweed x86_64 CLI binary/source RPMs,
|
||||||
|
a notice-bearing Linux x86_64 CLI tar.xz, an architecture-independent GUI
|
||||||
|
DEB, a noarch GUI RPM and matching source RPM, a source-only portable GUI
|
||||||
|
ZIP, a CLI Windows x86_64 ZIP containing the executable and notices, and a
|
||||||
|
native-architecture CLI macOS DMG.
|
||||||
|
Each binary format is built separately from the tagged source and may be
|
||||||
|
published only after its target-specific package and functional gates pass;
|
||||||
|
`SHA256SUMS` covers the promoted assets. Packages never enter Git or the
|
||||||
|
source archive.
|
||||||
|
- Exclude AppImage, AppDir and Flatpak bundles, GUI platform installers, and
|
||||||
|
bare executables from the 5.2.2 release set: the inspected type-2 runtime's
|
||||||
|
static dependency notice omitted mimalloc and the available inputs did not
|
||||||
|
provide a complete LGPL source/relink handoff. The offline helper now
|
||||||
|
requires runtime-specific compliance material from its operator.
|
||||||
|
- Publish the Windows executable only inside its ZIP with AGPL, GPL,
|
||||||
|
BSD-2-Clause, BSD-3-Clause, CC0-1.0, toolchain-runtime, NOTICE, and
|
||||||
|
third-party notices; no bare EXE is promoted.
|
||||||
|
- Limit the 5.2.2 GUI artifact promise to `zupt-gui_5.2.2_all.deb`,
|
||||||
|
`zupt-gui-5.2.2-1.noarch.rpm`, its matching source RPM, and
|
||||||
|
`zupt-gui-5.2.2-portable.zip`. Package artifacts have exact dependency and
|
||||||
|
installed off-screen GUI/CLI tests. The portable ZIP contains source and
|
||||||
|
launchers only and passes source scans, an exact safe-member allowlist, and an
|
||||||
|
extracted off-screen launcher test. Windows and macOS artifacts remain
|
||||||
|
CLI-only.
|
||||||
|
- Remove the standalone GUI `setup.py` sdist/wheel route, whose outputs omitted
|
||||||
|
the complete AGPL and artwork-provenance payload. Reviewed GUI installers and
|
||||||
|
package helpers preserve those notices with every included icon.
|
||||||
|
- Updated the README, installation, distribution, security, audit, GUI, and
|
||||||
|
manual-page documentation for the 5.2.2 source-only workflow.
|
||||||
|
|
||||||
|
Validation results are recorded by the release process and the openSUSE
|
||||||
|
packaging README. A missing tool or unexecuted platform remains `SKIP`; this
|
||||||
|
entry does not claim successful OBS, architecture, Leap, or SLE builds without
|
||||||
|
corresponding evidence. Runs made before the final positional-AAD and
|
||||||
|
mandatory-AIT changes are intermediate diagnostic evidence, not final release
|
||||||
|
gates. Private-key creation/parsing, terminal-safe comment rendering, prompt
|
||||||
|
signal cleanup, explicit-Bash regression execution, and scanner bomb limits are
|
||||||
|
also final self-audit release blockers. Their focused checks and the complete
|
||||||
|
required suite remain `PENDING` until the exact candidate is rerun before
|
||||||
|
tagging; this entry does not claim a final `PASS`.
|
||||||
|
|
||||||
|
|
||||||
## [5.2.1] — 2026-07-12 — GUI Verify/Extract robustness; refreshed comparison + audit tables
|
## [5.2.1] — 2026-07-12 — GUI Verify/Extract robustness; refreshed comparison + audit tables
|
||||||
|
|
@ -456,9 +701,10 @@ later + commercial; its own suite: 66/66):
|
||||||
pristine upstream files.
|
pristine upstream files.
|
||||||
- `VV_SOURCES` gained `vv_bcj.c` (the `test-asan` target could not link
|
- `VV_SOURCES` gained `vv_bcj.c` (the `test-asan` target could not link
|
||||||
since BCJ arrived).
|
since BCJ arrived).
|
||||||
- Corrected a Makefile comment that misstated the codec license as
|
- Corrected a Makefile comment that misstated the current codec license as
|
||||||
"Apache-2.0 / MIT" — the codec is **GPL-3.0-or-later**; the tool is
|
"Apache-2.0 / MIT" — the current codec is **GPL-3.0-or-later** and the
|
||||||
**AGPL-3.0-or-later** (never MIT).
|
current tool is **AGPL-3.0-or-later**. See the 5.2.2 licensing erratum for
|
||||||
|
preserved historical MIT grants.
|
||||||
|
|
||||||
### Compatibility summary
|
### Compatibility summary
|
||||||
|
|
||||||
|
|
@ -1513,10 +1759,10 @@ The v3.0.0 GUI's about panel had a credit line:
|
||||||
zupt Cristian Cezar Moises MIT
|
zupt Cristian Cezar Moises MIT
|
||||||
```
|
```
|
||||||
|
|
||||||
That was false. The GUI's SPDX header has always been
|
That entry described the intended license of the then-current GUI, but its
|
||||||
`AGPL-3.0-or-later`, the top-level `LICENSE` is AGPL, and the project
|
historical conclusion was incorrect. Earlier published repository revisions
|
||||||
policy is **AGPL-3.0-or-later with commercial dual-licensing forever**.
|
did contain MIT license notices, and those grants cannot be retroactively
|
||||||
The MIT line was a templating mistake inherited from an early scaffold.
|
denied. See the 5.2.2 licensing erratum above.
|
||||||
|
|
||||||
Removed. The CREDITS block now has two correctly-attributed rows:
|
Removed. The CREDITS block now has two correctly-attributed rows:
|
||||||
|
|
||||||
|
|
@ -1525,10 +1771,10 @@ Removed. The CREDITS block now has two correctly-attributed rows:
|
||||||
|
|
||||||
Both rows carry the commercial-licensing contact `sac@securityops.co`.
|
Both rows carry the commercial-licensing contact `sac@securityops.co`.
|
||||||
|
|
||||||
`gui/LICENSE-GUI` was an actual MIT license file. Replaced with the
|
`gui/LICENSE-GUI` was changed from an MIT-form file to an
|
||||||
AGPL-3.0-or-later text + commercial-dual-licensing note + a historical
|
AGPL-3.0-or-later notice for the then-current source. That change did not revoke
|
||||||
note explaining the prior MIT mistake (so anyone with an old tarball
|
MIT permissions already conveyed for historical material. The current
|
||||||
can't legitimately claim to have received an MIT grant).
|
`gui/LICENSE-GUI` records both the current notice and the factual erratum.
|
||||||
|
|
||||||
Top-level `LICENSE` preamble updated to reflect the Zupt → VaptVupt
|
Top-level `LICENSE` preamble updated to reflect the Zupt → VaptVupt
|
||||||
rename.
|
rename.
|
||||||
|
|
@ -1576,9 +1822,10 @@ All call sites updated.
|
||||||
|
|
||||||
### New regression test: `tests/test_gui_branding.sh`
|
### New regression test: `tests/test_gui_branding.sh`
|
||||||
|
|
||||||
11 assertions covering exactly the bugs we just fixed:
|
Assertions covering the current branding and license presentation:
|
||||||
- No MIT references in the GUI source (excluding the explanatory comment)
|
- No claim in the GUI source that the current GUI is MIT-only
|
||||||
- `gui/LICENSE-GUI` is AGPL-licensed and does not start with "MIT License"
|
- `gui/LICENSE-GUI` presents the current AGPL notice first and preserves the
|
||||||
|
evidenced historical MIT grant (see the 5.2.2 erratum)
|
||||||
- GUI source SPDX header is `AGPL-3.0-or-later`
|
- GUI source SPDX header is `AGPL-3.0-or-later`
|
||||||
- No `replace("zupt ", ...)` parser in code
|
- No `replace("zupt ", ...)` parser in code
|
||||||
- An anchored `_VERSION_RE` regex is present
|
- An anchored `_VERSION_RE` regex is present
|
||||||
|
|
@ -2090,7 +2337,8 @@ Covers, with appropriate plain-language honesty:
|
||||||
(encrypted modes), byte-level tamper detection (0 silent accepts
|
(encrypted modes), byte-level tamper detection (0 silent accepts
|
||||||
in v1.6 sweep), authentication-failure indistinguishability
|
in v1.6 sweep), authentication-failure indistinguishability
|
||||||
(F-11), post-quantum forward secrecy in `--pq-sdk`, side-channel
|
(F-11), post-quantum forward secrecy in `--pq-sdk`, side-channel
|
||||||
resistance on Jasmin-proven hot paths
|
resistance on hot paths then described as Jasmin-proven (5.2.2 records that
|
||||||
|
no reproducible formal-proof artifact was retained)
|
||||||
- **What Zupt does NOT protect against**: compromised endpoints,
|
- **What Zupt does NOT protect against**: compromised endpoints,
|
||||||
key compromise (no forward secrecy across archives, no rotation
|
key compromise (no forward secrecy across archives, no rotation
|
||||||
feature), weak passwords (with concrete brute-force numbers),
|
feature), weak passwords (with concrete brute-force numbers),
|
||||||
|
|
@ -3651,10 +3899,9 @@ yml, and a few packaging files had no SPDX line at all. Added:
|
||||||
it: `vv_ans.c`, `vv_decoder.c`, `vv_encoder.c`, `vv_huffman.c`,
|
it: `vv_ans.c`, `vv_decoder.c`, `vv_encoder.c`, `vv_huffman.c`,
|
||||||
`vv_simd.c`, `vv_xxh64.c`, `vv_ans.h`, `vv_huffman.h`)
|
`vv_simd.c`, `vv_xxh64.c`, `vv_ans.h`, `vv_huffman.h`)
|
||||||
|
|
||||||
The 5 Jasmin `.jazz` source files previously declared "MIT License" in
|
The current headers of five Jasmin `.jazz` source files were changed from MIT
|
||||||
their headers as a copy-paste artifact from an earlier draft. They have
|
notices to AGPL-3.0-or-later notices. This describes the current revision; it
|
||||||
been **relicensed to AGPL-3.0-or-later** (sole-author relicensing — no
|
does not revoke the MIT permissions attached to exact historical material.
|
||||||
external contributor's work was relicensed).
|
|
||||||
|
|
||||||
The 5 VaptVupt headers in `vendor/zuptsdk/include/` (vaptvupt.h,
|
The 5 VaptVupt headers in `vendor/zuptsdk/include/` (vaptvupt.h,
|
||||||
vaptvupt_api.h, vv_ans.h, vv_huffman.h, vv_platform.h) were tagged
|
vaptvupt_api.h, vv_ans.h, vv_huffman.h, vv_platform.h) were tagged
|
||||||
|
|
@ -4135,7 +4382,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
||||||
|
|
||||||
### Added — Block-Level Deduplication (`--dedup`)
|
### Added — Block-Level Deduplication (`--dedup`)
|
||||||
|
|
||||||
- **New `--dedup` / `-D` flag** for `zupt compress` and `zupt disk backup`. Eliminates redundant data blocks before compression using XXH64 fingerprinting with full content verification on match.
|
- **New `--dedup` / `-D` flag** for `zupt compress` and `zupt disk backup`. Eliminates redundant data blocks before compression using XXH64 fingerprinting (strengthened with an independent SHA-256/128 match in 5.2.2).
|
||||||
- **New block type `ZUPT_BLOCK_DEDUP_REF` (0x04)**: Reference blocks store an 8-byte offset to the original data block instead of the full block payload. A 4MB duplicate block becomes 8 bytes.
|
- **New block type `ZUPT_BLOCK_DEDUP_REF` (0x04)**: Reference blocks store an 8-byte offset to the original data block instead of the full block payload. A 4MB duplicate block becomes 8 bytes.
|
||||||
- **Hash table index**: Open-addressing with linear probing, capped at 2M entries (~48MB RAM). 75% load factor limit. Secure wipe on free.
|
- **Hash table index**: Open-addressing with linear probing, capped at 2M entries (~48MB RAM). 75% load factor limit. Secure wipe on free.
|
||||||
- **Content verification**: XXH64 fingerprint match is verified by block size comparison to prevent hash-collision corruption.
|
- **Content verification**: XXH64 fingerprint match is verified by block size comparison to prevent hash-collision corruption.
|
||||||
|
|
@ -4310,8 +4557,8 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
||||||
## [1.5.0] — 2026-03-28
|
## [1.5.0] — 2026-03-28
|
||||||
|
|
||||||
### Added — Jasmin Assembly Integration (Sprint 1)
|
### Added — Jasmin Assembly Integration (Sprint 1)
|
||||||
- **`zupt_mac_verify_ct`** Jasmin assembly linked into `zupt_decrypt_buffer()`. Replaces the C XOR accumulation loop for HMAC-SHA256 comparison. 4×u64 unrolled XOR, proven constant-time by Jasmin type system. Symbol confirmed active via `nm`: `T zupt_mac_verify_ct`.
|
- **`zupt_mac_verify_ct`** Jasmin assembly linked into `zupt_decrypt_buffer()`. Replaces the C XOR accumulation loop for HMAC-SHA256 comparison. 4×u64 unrolled XOR, then described as proven constant-time; 5.2.2 records that no reproducible proof artifact was retained. Symbol confirmed active via `nm`: `T zupt_mac_verify_ct`.
|
||||||
- **`zupt_ct_select_32`** Jasmin assembly linked into `zupt_mlkem768_decaps()`. Replaces the C `cmov()` function for Fujisaki-Okamoto implicit rejection key selection. 4×u64 masked select, proven constant-time. Symbol confirmed active via `nm`: `T zupt_ct_select_32`.
|
- **`zupt_ct_select_32`** Jasmin assembly linked into `zupt_mlkem768_decaps()`. Replaces the C `cmov()` function for Fujisaki-Okamoto implicit rejection key selection. 4×u64 masked select, with the same historical proof qualification above. Symbol confirmed active via `nm`: `T zupt_ct_select_32`.
|
||||||
- **`include/zupt_jasmin.h`** — extern declarations for all Jasmin functions with ABI documentation.
|
- **`include/zupt_jasmin.h`** — extern declarations for all Jasmin functions with ABI documentation.
|
||||||
- **`#ifdef ZUPT_USE_JASMIN`** dispatch guards in `zupt_crypto.c` and `zupt_mlkem.c` with clean C fallback.
|
- **`#ifdef ZUPT_USE_JASMIN`** dispatch guards in `zupt_crypto.c` and `zupt_mlkem.c` with clean C fallback.
|
||||||
- **Makefile** auto-detects `jasmin/*.s` files, assembles to `.o`, links into binary, sets `-DZUPT_USE_JASMIN`.
|
- **Makefile** auto-detects `jasmin/*.s` files, assembles to `.o`, links into binary, sets `-DZUPT_USE_JASMIN`.
|
||||||
|
|
@ -4343,8 +4590,9 @@ All 4 `.jazz` files rewritten to fix compilation errors:
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Removed all `-CT` flag references (does not exist in jasminc 2026.03.0).
|
- Removed all `-CT` flag references (does not exist in jasminc 2026.03.0).
|
||||||
- CT enforced by Jasmin type system during normal compilation.
|
- The release claimed CT enforcement by the Jasmin type system during normal
|
||||||
- Safety: `jasminc -arch x86-64 -checksafety`.
|
compilation and use of `jasminc -arch x86-64 -checksafety`; no reproducible
|
||||||
|
certificate/log for those claims was retained (documented in 5.2.2).
|
||||||
- All compound expressions split into separate register operations.
|
- All compound expressions split into separate register operations.
|
||||||
- All output parameters changed from `reg ptr` to `reg u64` raw pointers.
|
- All output parameters changed from `reg ptr` to `reg u64` raw pointers.
|
||||||
- Byte-level access avoided: 4×u64 instead of 32×u8.
|
- Byte-level access avoided: 4×u64 instead of 32×u8.
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
|
||||||
project(zupt VERSION 0.4.0 LANGUAGES C)
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
|
||||||
set(SOURCES
|
|
||||||
src/zupt_main.c src/zupt_format.c src/zupt_lz.c src/zupt_lzh.c src/zupt_xxh.c
|
|
||||||
src/zupt_sha256.c src/zupt_aes256.c src/zupt_crypto.c src/zupt_predict.c)
|
|
||||||
add_executable(zupt ${SOURCES})
|
|
||||||
target_include_directories(zupt PRIVATE include)
|
|
||||||
target_link_libraries(zupt m)
|
|
||||||
if(MSVC)
|
|
||||||
target_compile_options(zupt PRIVATE /W4 /D_CRT_SECURE_NO_WARNINGS)
|
|
||||||
else()
|
|
||||||
target_compile_options(zupt PRIVATE -Wall -Wextra -O2)
|
|
||||||
endif()
|
|
||||||
install(TARGETS zupt DESTINATION bin)
|
|
||||||
434
DISTRIBUTION.md
434
DISTRIBUTION.md
|
|
@ -1,258 +1,290 @@
|
||||||
# Distributing VaptVupt
|
# Distributing ZUPT 5.2.2
|
||||||
|
|
||||||
This document describes the upstream packaging recipes shipped under `packaging/` and the path from a local source tree to an installable package.
|
This document describes the packaging material maintained in the ZUPT
|
||||||
|
source repository. A recipe in `packaging/` is not evidence that a package has
|
||||||
|
been accepted by a distribution or that every target platform has been tested.
|
||||||
|
Record each build and test result separately; an unexecuted target is a skip.
|
||||||
|
|
||||||
Real submission to AUR / Debian / Fedora / Homebrew / NixOS / openSUSE is operational work outside this repository.
|
The canonical repository is:
|
||||||
|
|
||||||
## Producing a reproducible source tarball
|
```text
|
||||||
|
https://github.com/cristiancmoises/zupt
|
||||||
Every packaging recipe expects an upstream tarball `vaptvupt-VERSION.tar.gz` produced by the project's `make dist` target. The tarball is byte-reproducible:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
make dist
|
|
||||||
# → /tmp/vaptvupt-5.0.0.tar.gz
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Re-running `make dist` on the same source tree produces an identical sha256 (verified by `tests/test_dist_reproducible.sh`, wired into `make test`). This lets distros pin a stable hash in their recipes.
|
GitHub is the canonical source and release host. Packaging must never fetch
|
||||||
|
`zupt-web` or substitute an asset from another project.
|
||||||
|
|
||||||
The reproducibility properties:
|
## Source-only boundary
|
||||||
|
|
||||||
- Files sorted by name (deterministic order across filesystems)
|
Git, `git archive`, and the upstream source tarball contain source code,
|
||||||
- mtime fixed to `SOURCE_DATE_EPOCH` (default `1747699200`; override via env)
|
textual assembly, documentation, packaging metadata, and necessary data files.
|
||||||
- uid/gid pinned to root (0/0) via `--owner=0 --group=0 --numeric-owner`
|
They do not contain compiled objects or executables, shared or static libraries,
|
||||||
- gzip wrapped with `-9n` (no embedded timestamp or filename)
|
or DEB/RPM/AppImage packages.
|
||||||
- Source-only — no `.o`, no built binaries, no `.git/` tree
|
|
||||||
|
|
||||||
The tree is source-only. The default `make` build needs only a C compiler, make, libm, and pthread — no external crypto library, and it installs no `.so`. The optional SDK-backed modes (`--pq-sdk`, `--pq-box`) and the Argon2id KDF are built only with `make WITH_SDK=1` against the separately distributed `libzuptsdk` / `libpqvaptvupt` libraries.
|
The default build is deliberately independent of the optional SDK and PQBOX
|
||||||
|
libraries:
|
||||||
To force a specific epoch (for distro release-day pinning):
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
SOURCE_DATE_EPOCH=1727740800 make dist # 2024-10-01 UTC
|
make clean
|
||||||
|
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 1)" \
|
||||||
|
WITH_SDK=0 WITH_PQBOX=0
|
||||||
|
make WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
```
|
```
|
||||||
|
|
||||||
## Recipes shipped
|
`WITH_SDK=1` and `WITH_PQBOX=1` use separately installed system development
|
||||||
|
libraries. They never load a library committed under `vendor/`, never download
|
||||||
|
a dependency during build or test, and fail explicitly when their development
|
||||||
|
metadata is unavailable. Distribution builds should keep both options at `0`
|
||||||
|
unless the corresponding source-built system packages are declared as build
|
||||||
|
requirements.
|
||||||
|
|
||||||
| Distro / Platform | Path | Format |
|
Audit the current tree or a generated archive with:
|
||||||
|-------------------|---------------------------------|----------------|
|
|
||||||
| Arch Linux | `packaging/aur/PKGBUILD` | AUR PKGBUILD |
|
|
||||||
| Debian / Ubuntu | `packaging/debian/` | Source package (`3.0 (quilt)`) |
|
|
||||||
| Fedora / RHEL | `packaging/rpm/vaptvupt.spec` | RPM .spec |
|
|
||||||
| openSUSE | `packaging/opensuse/` | RPM .spec (OBS) |
|
|
||||||
| macOS | `packaging/homebrew/vaptvupt.rb`| Homebrew formula |
|
|
||||||
| NixOS / Nix flake | `packaging/nix/flake.nix` | Nix flake |
|
|
||||||
|
|
||||||
All recipes:
|
|
||||||
|
|
||||||
- Install the binary to `$PREFIX/bin/vaptvupt` (default `/usr/bin/vaptvupt`)
|
|
||||||
- Install manpage to `$PREFIX/share/man/man1/vaptvupt.1.gz`
|
|
||||||
- Install docs (README, SECURITY, CHANGELOG) to `$PREFIX/share/doc/vaptvupt/`
|
|
||||||
- Run the full upstream regression suite (`make test`) during build when the distro's package guidelines allow check-phase execution
|
|
||||||
|
|
||||||
## Arch Linux (AUR)
|
|
||||||
|
|
||||||
Maintainer flow:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# 1. Produce the upstream tarball
|
scripts/check-source-only.sh
|
||||||
make dist
|
scripts/check-source-only.sh --archive /path/to/zupt-5.2.2.tar.gz
|
||||||
# → /tmp/vaptvupt-5.0.0.tar.gz
|
|
||||||
|
|
||||||
# 2. Upload to a stable URL (e.g. git.securityops.co releases)
|
|
||||||
|
|
||||||
# 3. Update packaging/aur/PKGBUILD:
|
|
||||||
# - Set pkgver=5.0.0
|
|
||||||
# - Set sha256sums=("$(sha256sum /tmp/vaptvupt-5.0.0.tar.gz | awk '{print $1}')")
|
|
||||||
|
|
||||||
# 4. Generate .SRCINFO
|
|
||||||
cd packaging/aur && makepkg --printsrcinfo > .SRCINFO
|
|
||||||
|
|
||||||
# 5. Test locally
|
|
||||||
makepkg -s
|
|
||||||
|
|
||||||
# 6. Push to AUR
|
|
||||||
git clone ssh://aur@aur.archlinux.org/vaptvupt.git aur-vaptvupt
|
|
||||||
cp packaging/aur/PKGBUILD packaging/aur/.SRCINFO aur-vaptvupt/
|
|
||||||
cd aur-vaptvupt && git add -A && git commit -m "v5.0.0" && git push
|
|
||||||
```
|
```
|
||||||
|
|
||||||
User install:
|
The scanner reports paths, not file contents, and exits nonzero on a violation.
|
||||||
|
|
||||||
|
## Reproducible source archive
|
||||||
|
|
||||||
|
`make dist` verifies committed `HEAD` and exports its tree object, normalizes
|
||||||
|
member order, timestamps, owner/group metadata, and gzip metadata, and audits
|
||||||
|
the result before moving it to its destination. Exporting the tree rather than
|
||||||
|
the commit omits Git's commit-ID PAX header:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
yay -S vaptvupt # or paru, pikaur, etc.
|
SOURCE_DATE_EPOCH="$(git show -s --format=%ct HEAD)" \
|
||||||
|
make DIST_TARBALL=/tmp/zupt-5.2.2.tar.gz dist
|
||||||
|
sha256sum /tmp/zupt-5.2.2.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
## Shell completions
|
With identical committed input and `SOURCE_DATE_EPOCH`, repeated exports must
|
||||||
|
have the same SHA-256 digest. Do not generate a release tarball from uncommitted
|
||||||
|
working-tree files.
|
||||||
|
|
||||||
`make install` automatically installs Bash, zsh, and fish completion files alongside the binary and manpage:
|
The AUR, Homebrew, and Guix recipes pin the checksum of this tarball. They are
|
||||||
|
marked `export-ignore` in `.gitattributes` so their own checksum fields do not
|
||||||
|
make the archive self-referential. A commit changing only those ignored recipes
|
||||||
|
therefore leaves the fixed-epoch archive byte-identical. The recipes remain
|
||||||
|
versioned in Git and must be updated after the final source archive checksum is
|
||||||
|
known.
|
||||||
|
|
||||||
| Shell | Path |
|
Do not commit the generated tarball or checksum file. Host them as immutable
|
||||||
|---|---|
|
release assets after the release tag is published.
|
||||||
| Bash | `$PREFIX/share/bash-completion/completions/vaptvupt` |
|
|
||||||
| zsh | `$PREFIX/share/zsh/site-functions/_vaptvupt` |
|
|
||||||
| fish | `$PREFIX/share/fish/vendor_completions.d/vaptvupt.fish` |
|
|
||||||
|
|
||||||
The source files live under `completions/` in the project tree. Distros that prefer a different install location should override the relevant paths in their `make install` invocation.
|
## Staged installation
|
||||||
|
|
||||||
For per-user installation without root:
|
Packagers should preserve distribution flags and install into a package root:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Bash
|
make -j"${JOBS:-1}" WITH_SDK=0 WITH_PQBOX=0 \
|
||||||
cp completions/vaptvupt.bash ~/.local/share/bash-completion/completions/vaptvupt
|
CPPFLAGS="$CPPFLAGS" CFLAGS="$CFLAGS" \
|
||||||
|
LDFLAGS="$LDFLAGS" LDLIBS="$LDLIBS"
|
||||||
# zsh (somewhere in $fpath; add the directory to ~/.zshrc if needed)
|
make WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
cp completions/_vaptvupt ~/.zsh/completion/_vaptvupt
|
make DESTDIR="$pkgroot" PREFIX=/usr \
|
||||||
|
WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 install
|
||||||
# fish
|
|
||||||
cp completions/vaptvupt.fish ~/.config/fish/completions/vaptvupt.fish
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Completions cover every CLI flag the binary actually parses (`--kdf`, `--comment`, `--comment-file`, `--pq`, `--dedup`, etc.) and are validated on every CI run via `tests/test_completions_manpage.sh`.
|
`INSTALL_LEGACY_ALIAS=0` installs only `zupt`. The `vaptvupt`
|
||||||
|
command can be requested explicitly with `INSTALL_LEGACY_ALIAS=1`, but it is
|
||||||
|
not installed by default and is not part of the openSUSE main package. This
|
||||||
|
keeps the canonical package surface limited to ZUPT and `zupt`.
|
||||||
|
|
||||||
## Debian / Ubuntu
|
The Makefile accepts the usual `BINDIR`, `LIBDIR`, `INCLUDEDIR`, `MANDIR`, and
|
||||||
|
completion-directory overrides. It does not strip package builds or add a
|
||||||
|
private-library RPATH.
|
||||||
|
|
||||||
The `packaging/debian/` tree is a Debian source-package layout. Maintainer flow:
|
## Packaging material
|
||||||
|
|
||||||
|
| Target | Maintained path | Intended output |
|
||||||
|
|---|---|---|
|
||||||
|
| openSUSE / OBS | `packaging/opensuse/` | source and binary RPM through OBS |
|
||||||
|
| Debian / Ubuntu | `packaging/debian/`, `packaging/build-deb.sh` | Debian metadata and binary DEB after the target gate |
|
||||||
|
| RPM release artifact | `packaging/opensuse/zupt.spec`, `packaging/build-rpm.sh` | source and binary RPM after the target gate |
|
||||||
|
| GUI DEB | `packaging/build-gui-deb.sh` | `zupt-gui_5.2.2_all.deb` after payload/dependency and installed integration gates |
|
||||||
|
| GUI RPM | `packaging/build-gui-rpm.sh` | `zupt-gui-5.2.2-1.noarch.rpm` and matching `.src.rpm` after package and installed integration gates |
|
||||||
|
| Linux CLI archive | `.github/workflows/ci.yml` | `zupt-5.2.2-linux-x86_64.tar.xz` with notices after dependency, member, and extracted functional gates |
|
||||||
|
| Portable GUI source | `packaging/portable/`, `.github/workflows/ci.yml` | `zupt-gui-5.2.2-portable.zip` after source scan, member allowlist, and extracted off-screen integration gate |
|
||||||
|
| Fedora / RPM-based systems | `packaging/rpm/zupt.spec` | downstream RPM starting point |
|
||||||
|
| AppImage helper | `packaging/build-appimage.sh` | downstream-only helper; no 5.2.2 AppImage is promoted |
|
||||||
|
| Windows | `.github/workflows/cross-platform.yml` | native ZIP (executable plus notices) after the required native gate |
|
||||||
|
| macOS | `packaging/build-dmg.sh` | native-architecture DMG after the native gate |
|
||||||
|
| Arch Linux | `packaging/aur/PKGBUILD` | AUR package recipe |
|
||||||
|
| Homebrew | `packaging/homebrew/zupt.rb` | formula-built package |
|
||||||
|
| Guix | `packaging/guix/zupt.scm` | Guix package definition |
|
||||||
|
| Nix | `packaging/nix/flake.nix` | flake-built package |
|
||||||
|
|
||||||
|
These files are upstream starting points. Use each distribution's isolated
|
||||||
|
builder and current policy checks; do not claim support based only on parsing a
|
||||||
|
recipe.
|
||||||
|
|
||||||
|
### openSUSE / OBS
|
||||||
|
|
||||||
|
The authoritative instructions, tested matrix, and outstanding gates are in
|
||||||
|
`packaging/opensuse/README.md`. The normal local flow is:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# 1. Produce the upstream tarball with the standard Debian
|
cd packaging/opensuse
|
||||||
# orig.tar.gz naming convention:
|
xmllint --noout _service
|
||||||
make dist
|
osc service manualrun
|
||||||
cp /tmp/vaptvupt-5.0.0.tar.gz /tmp/vaptvupt_5.0.0.orig.tar.gz
|
rpmspec -P zupt.spec >/dev/null
|
||||||
|
osc build openSUSE_Tumbleweed x86_64 zupt.spec
|
||||||
# 2. Unpack and overlay the debian/ tree:
|
|
||||||
cd /tmp && tar xzf vaptvupt_5.0.0.orig.tar.gz && cd vaptvupt-5.0.0
|
|
||||||
cp -a /path/to/vaptvupt/packaging/debian ./debian
|
|
||||||
|
|
||||||
# 3. Build the source package:
|
|
||||||
dpkg-buildpackage -S -us -uc # source-only
|
|
||||||
dpkg-buildpackage -b -us -uc # binary
|
|
||||||
|
|
||||||
# 4. Lint:
|
|
||||||
lintian vaptvupt_5.0.0-1_*.deb
|
|
||||||
|
|
||||||
# 5. Submit via the standard Debian mentors process:
|
|
||||||
# https://mentors.debian.net/intro-maintainers/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
User install (after the package lands in Debian unstable / Ubuntu):
|
Run `rpmlint` on all produced RPMs and install the binary RPM in a disposable
|
||||||
|
environment for `--version`, `--help`, and archive round-trip tests. Presence of
|
||||||
|
the OBS files upstream does not mean the package has been submitted or accepted
|
||||||
|
by openSUSE Factory.
|
||||||
|
|
||||||
|
### Debian and RPM release artifacts
|
||||||
|
|
||||||
|
The release helper scripts build from this source tree, stage into temporary
|
||||||
|
directories, run their format and installed-binary checks, and place only their
|
||||||
|
final outputs in an explicitly selected directory. Run them from an exact
|
||||||
|
checkout of the immutable tag inside a clean target container, chroot, or VM:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt install vaptvupt
|
release_dir=$(mktemp -d)
|
||||||
|
|
||||||
|
# Native Debian/Ubuntu binary package
|
||||||
|
DIST_DIR="$release_dir" RUN_CHECKS=1 packaging/build-deb.sh
|
||||||
|
|
||||||
|
# Source and binary RPM using the openSUSE spec
|
||||||
|
DIST_DIR="$release_dir" packaging/build-rpm.sh
|
||||||
|
|
||||||
|
# Architecture-independent GUI DEB and noarch/source GUI RPM
|
||||||
|
DIST_DIR="$release_dir" packaging/build-gui-deb.sh
|
||||||
|
DIST_DIR="$release_dir" packaging/build-gui-rpm.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Fedora / RHEL / CentOS
|
`packaging/build-deb.sh` creates a native binary DEB; it does not claim to
|
||||||
|
create a Debian source package. The files in `packaging/debian/` are Debian
|
||||||
|
source-package metadata and must be staged as the source package's top-level
|
||||||
|
`debian/` directory before using `dpkg-buildpackage`. Running
|
||||||
|
`dpkg-buildpackage` directly at the ZUPT repository root is not the
|
||||||
|
documented release-artifact path.
|
||||||
|
|
||||||
|
`packaging/build-rpm.sh` creates its audited Source0 archive, builds both the
|
||||||
|
binary RPM and source RPM, inspects the installed payload, and copies both
|
||||||
|
outputs to `DIST_DIR`. The separate `packaging/rpm/zupt.spec` is a
|
||||||
|
Fedora-family downstream starting point; build and lint it only after staging
|
||||||
|
Source0 in a normal RPM build tree.
|
||||||
|
|
||||||
|
Run the target's metadata and lint tools in addition to the script gates. A
|
||||||
|
package built for one distribution release or architecture is not evidence for
|
||||||
|
another.
|
||||||
|
|
||||||
|
The GUI helpers package Python/Qt source rather than compiled application code.
|
||||||
|
They validate exact version, payload, dependency, ownership and legacy-alias
|
||||||
|
expectations, then test the installed launcher off-screen against the matching
|
||||||
|
`zupt` CLI. A successful GUI DEB gate does not imply an RPM gate, or vice versa.
|
||||||
|
|
||||||
|
### Portable and native release artifacts
|
||||||
|
|
||||||
|
The Linux x86_64 gate packages the tested `zupt` executable as
|
||||||
|
`zupt-5.2.2-linux-x86_64.tar.xz` beside README, changelog, security guidance,
|
||||||
|
and every applicable public license and notice. Its dynamic-library allowlist,
|
||||||
|
archive member allowlist, and extracted CLI functional suite must pass.
|
||||||
|
|
||||||
|
The `zupt-gui-5.2.2-portable.zip` artifact is source-only: it contains the GUI
|
||||||
|
Python source, shell/macOS/Windows launchers, icons, provenance, changelog, and
|
||||||
|
licenses, but no Python, Qt, CLI, or compiled runtime. The gate scans both the
|
||||||
|
assembled and extracted trees, verifies an exact safe member allowlist, and
|
||||||
|
runs the extracted launcher off-screen against the tested CLI.
|
||||||
|
|
||||||
|
AppImage creation is deliberately offline and is not a 5.2.2 release gate.
|
||||||
|
Supply a locally verified `appimagetool`, type-2 runtime, and the complete
|
||||||
|
license/source-relink compliance notice for those exact runtime bytes; the
|
||||||
|
helper never downloads any input:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# 1. Produce the tarball
|
DIST_DIR="$release_dir" RUN_CHECKS=1 \
|
||||||
make dist
|
APPIMAGETOOL=/verified/path/appimagetool \
|
||||||
cp /tmp/vaptvupt-5.0.0.tar.gz ~/rpmbuild/SOURCES/
|
APPIMAGE_RUNTIME_FILE=/verified/path/runtime-x86_64 \
|
||||||
|
APPIMAGE_RUNTIME_COMPLIANCE_FILE=/verified/path/runtime-compliance.txt \
|
||||||
# 2. Drop the .spec into the SPECS directory:
|
packaging/build-appimage.sh
|
||||||
cp packaging/rpm/vaptvupt.spec ~/rpmbuild/SPECS/
|
|
||||||
|
|
||||||
# 3. Build source + binary RPMs:
|
|
||||||
cd ~/rpmbuild && rpmbuild -ba SPECS/vaptvupt.spec
|
|
||||||
|
|
||||||
# 4. Lint:
|
|
||||||
rpmlint RPMS/x86_64/vaptvupt-5.0.0-1.fc*.rpm
|
|
||||||
|
|
||||||
# 5. Submit via the Fedora new-package review process:
|
|
||||||
# https://docs.fedoraproject.org/en-US/package-maintainers/Package_Review_Process/
|
|
||||||
# EPEL automatically inherits Fedora packages.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
User install (after the package lands in Fedora / EPEL):
|
The runtime inspected while preparing 5.2.2 omitted a linked component from
|
||||||
|
its notice and did not provide the complete LGPL source/relink handoff required
|
||||||
|
by this release policy. No AppImage produced by this helper is promoted by the
|
||||||
|
upstream 5.2.2 workflow. AppDir and Flatpak bundles and GUI platform installers
|
||||||
|
are also excluded. Bare Linux and Windows executables are not promoted; their
|
||||||
|
CLI programs appear only inside notice-bearing archives. The Windows ZIP and
|
||||||
|
macOS DMG remain CLI-only.
|
||||||
|
|
||||||
|
Run `packaging/build-dmg.sh` only on a native macOS host. It records the host
|
||||||
|
architecture in the filename and tests the binary before and after packaging:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo dnf install vaptvupt # Fedora
|
DIST_DIR="$release_dir" RUN_CHECKS=1 packaging/build-dmg.sh
|
||||||
sudo dnf install epel-release vaptvupt # RHEL/CentOS via EPEL
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## openSUSE
|
The Windows ZIP (including its executable and notices) must be built and tested
|
||||||
|
by the Windows job in `.github/workflows/cross-platform.yml`; it is not a
|
||||||
|
cross-compiled release claim from a Linux build. No Wine result is retained as
|
||||||
|
5.2.2 release evidence. Extended-length/device namespace paths, raw UNC output
|
||||||
|
roots, and mapped/network-drive output are not supported in 5.2.2. Publish the
|
||||||
|
exact architecture recorded by the native job.
|
||||||
|
These helpers create binary distribution artifacts for the release page, not
|
||||||
|
content to be committed to Git or included in the source archive.
|
||||||
|
|
||||||
The `packaging/opensuse/` tree carries an RPM `.spec` suited to the Open Build Service (OBS).
|
### AUR, Homebrew, Guix, and Nix
|
||||||
|
|
||||||
```sh
|
After calculating the final reproducible source archive, but before creating or
|
||||||
# 1. Produce the tarball
|
publishing the immutable tag, update each recipe to version 5.2.2 and to the
|
||||||
make dist
|
exact digest or content hash expected by its package manager. These recipe
|
||||||
|
directories are excluded from the source archive, so this does not create a
|
||||||
|
checksum cycle. Commit the pinned recipes in the tagged tree, then build and
|
||||||
|
test with each package manager before publishing its recipe. Keep build inputs
|
||||||
|
offline-capable: the check phase must not fetch source or dependencies
|
||||||
|
dynamically.
|
||||||
|
|
||||||
# 2. In an OBS package checkout (osc), stage the sources and spec:
|
## Release-page artifacts
|
||||||
cp /tmp/vaptvupt-5.0.0.tar.gz .
|
|
||||||
cp /path/to/vaptvupt/packaging/opensuse/vaptvupt.spec .
|
|
||||||
|
|
||||||
# 3. Build locally against a target repository:
|
The source-only policy applies to Git and upstream source archives. A release
|
||||||
osc build openSUSE_Tumbleweed x86_64
|
page may also carry CLI/GUI DEB and RPM artifacts, the notice-bearing Linux CLI
|
||||||
|
tar.xz, source-only portable GUI ZIP, CLI Windows ZIP, or CLI macOS DMG when
|
||||||
|
each is built from the tagged source in its target environment and passes its
|
||||||
|
format-specific tests. These are separate outputs, never inputs to a source
|
||||||
|
build.
|
||||||
|
|
||||||
# 4. Commit to OBS once the build and check phase pass:
|
For every published artifact:
|
||||||
osc addremove && osc commit
|
|
||||||
```
|
|
||||||
|
|
||||||
User install (after the package lands in a distribution or OBS repository):
|
1. start from the immutable `v5.2.2` tag;
|
||||||
|
2. keep `WITH_SDK=0 WITH_PQBOX=0` unless system dependencies are declared;
|
||||||
|
3. record the exact OS, distribution release, architecture, and toolchain;
|
||||||
|
4. run format validation plus installed `--version`, `--help`, and archive
|
||||||
|
round-trip tests;
|
||||||
|
5. publish a SHA-256 checksum;
|
||||||
|
6. scan the source inputs and ensure no credential or build path is embedded;
|
||||||
|
7. label an unbuilt or untested target `SKIP`, never `PASS`.
|
||||||
|
|
||||||
```sh
|
Do not infer multi-architecture compatibility from portable source. Do not add
|
||||||
sudo zypper install vaptvupt
|
precompiled optional libraries to make a package build.
|
||||||
```
|
|
||||||
|
|
||||||
## macOS (Homebrew)
|
Publish release assets at the canonical GitHub release. If an expected asset is
|
||||||
|
absent or has a different checksum, report that target as unpublished rather
|
||||||
|
than redirecting consumers to an unverified file.
|
||||||
|
|
||||||
```sh
|
## Downstream checklist
|
||||||
# 1. Produce the tarball and upload to a stable release URL.
|
|
||||||
|
|
||||||
# 2. Update packaging/homebrew/vaptvupt.rb:
|
- [ ] The source URL resolves to the immutable `v5.2.2` tag.
|
||||||
# - Set url to the release URL
|
- [ ] The source archive passes `scripts/check-source-only.sh --archive`.
|
||||||
# - Set sha256 to the upstream tarball sha256
|
- [ ] The recipe checksum matches the downloaded source exactly.
|
||||||
|
- [ ] `WITH_SDK=0 WITH_PQBOX=0` is explicit, or system dependencies are complete.
|
||||||
# 3. Test locally:
|
- [ ] Distribution compiler and linker flags are preserved.
|
||||||
brew install --build-from-source ./packaging/homebrew/vaptvupt.rb
|
- [ ] The real upstream `check` target runs without network access.
|
||||||
brew test vaptvupt
|
- [ ] Installation uses `DESTDIR` and does not write under `/usr/local`.
|
||||||
brew audit --strict --online vaptvupt
|
- [ ] The main package installs `zupt`; any `vaptvupt` alias is explicitly documented as compatibility-only.
|
||||||
|
- [ ] Licenses include AGPL-3.0-or-later for the application,
|
||||||
# 4. Submit to homebrew-core (preferred, requires popularity threshold):
|
GPL-3.0-or-later for the bundled source codec, and BSD-2-Clause for the
|
||||||
# https://docs.brew.sh/Adding-Software-to-Homebrew
|
xxHash-derived XXH64 routines, plus CC0-1.0 for the
|
||||||
#
|
pq-crystals/kyber-derived ML-KEM portions and BSD-3-Clause for the
|
||||||
# OR host in your own tap:
|
curve25519-donna-derived X25519 portions.
|
||||||
# https://docs.brew.sh/How-to-Create-and-Maintain-a-Tap
|
- [ ] Package contents, dependencies, hardening, RPATH/RUNPATH, and debug info
|
||||||
```
|
have been inspected with target-native tools.
|
||||||
|
- [ ] Installed-package smoke and round-trip tests pass.
|
||||||
User install (after submission lands):
|
- [ ] Only tested target artifacts are attached to the release.
|
||||||
|
|
||||||
```sh
|
|
||||||
brew install vaptvupt
|
|
||||||
# OR from a custom tap:
|
|
||||||
brew install cristiancmoises/tap/vaptvupt
|
|
||||||
```
|
|
||||||
|
|
||||||
## NixOS / Nix flake
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# 1. Build directly from the flake (no central submission needed):
|
|
||||||
nix build github:cristiancmoises/vaptvupt#vaptvupt
|
|
||||||
nix run github:cristiancmoises/vaptvupt#vaptvupt -- version
|
|
||||||
|
|
||||||
# 2. To consume from another flake:
|
|
||||||
# inputs.vaptvupt.url = "github:cristiancmoises/vaptvupt?ref=v5.0.0";
|
|
||||||
# packages.x86_64-linux.default = inputs.vaptvupt.packages.x86_64-linux.vaptvupt;
|
|
||||||
|
|
||||||
# 3. To submit to nixpkgs (https://github.com/NixOS/nixpkgs):
|
|
||||||
# - Adapt packaging/nix/flake.nix's `vaptvupt` derivation into a
|
|
||||||
# pkgs/by-name/va/vaptvupt/package.nix using fetchurl and a hash.
|
|
||||||
# - Follow the nixpkgs contribution guide:
|
|
||||||
# https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## Submitting upstream — checklist
|
|
||||||
|
|
||||||
Before pushing any recipe to a distro repository:
|
|
||||||
|
|
||||||
- [ ] `make dist` produces a reproducible tarball (verified by `tests/test_dist_reproducible.sh` on every `make test`)
|
|
||||||
- [ ] The tarball is uploaded to a stable, immutable URL
|
|
||||||
- [ ] The recipe's checksum field is updated to match `sha256sum /tmp/vaptvupt-VERSION.tar.gz`
|
|
||||||
- [ ] The recipe builds and tests pass in a clean chroot/container
|
|
||||||
- [ ] The CHANGELOG mentions distro-relevant changes since the last release
|
|
||||||
- [ ] The license metadata is correct (AGPL-3.0-or-later for VaptVupt core; GPL-3.0-or-later for the vendored VaptVupt codec)
|
|
||||||
|
|
||||||
## Security posture for downstream
|
|
||||||
|
|
||||||
Every packaging recipe runs `make test` during build (`check()` for AUR, `override_dh_auto_test` for Debian, `%check` for RPM and openSUSE, `checkPhase` for Nix, `test` block for Homebrew). The test suite runs in each recipe's check phase, including the tamper/integrity regressions and the `make dist` byte-identical reproducibility check.
|
|
||||||
|
|
||||||
A build that doesn't pass `make test` will fail at distro check time — the recipes don't paper over regressions.
|
|
||||||
|
|
|
||||||
401
INSTALL.md
401
INSTALL.md
|
|
@ -1,232 +1,227 @@
|
||||||
# VaptVupt + VaptVupt GUI — Install Guide for Linux
|
# Installing ZUPT 5.2.2
|
||||||
|
|
||||||
If you're seeing the error:
|
This guide covers the ZUPT command-line program and the optional Python GUI.
|
||||||
|
The canonical source repository is
|
||||||
|
`https://github.com/cristiancmoises/zupt`.
|
||||||
|
|
||||||
```
|
## Choosing an installation method
|
||||||
vaptvupt-gui depende de python3-pyqt6 | python3-pyside6; porém:
|
|
||||||
Pacote python3-pyqt6 não está instalado.
|
|
||||||
vaptvupt-gui depende de vaptvupt (>= 5.0.0); porém:
|
|
||||||
Versão de vaptvupt no sistema é 2.1.7-1.
|
|
||||||
```
|
|
||||||
|
|
||||||
This is correct behavior. The `vaptvupt-gui` deb requires:
|
- Build from the immutable source tag when you want the upstream source-only
|
||||||
- Python 3 with **PyQt6** or **PySide6** (the GUI toolkit)
|
path described below.
|
||||||
- The **vaptvupt CLI 5.0.0** or newer
|
- Use a distribution package only when it matches your distribution release
|
||||||
|
and architecture.
|
||||||
|
- Release-page DEB, RPM, Linux tar.xz, portable GUI ZIP, Windows ZIP, and macOS
|
||||||
|
files are separate artifacts. Their presence does not make them part of the
|
||||||
|
Git tree or upstream source archive. Use only artifacts whose release notes
|
||||||
|
record a successful format-specific test for your target.
|
||||||
|
|
||||||
## The fastest fix — one command (Linux Mint, Ubuntu, Debian)
|
The promoted 5.2.2 package set, only after each target gate succeeds, is:
|
||||||
|
|
||||||
Put all the downloaded files in the same folder, then:
|
| Component | Gated artifacts |
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo bash install-zupt-gui.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This script auto-detects your distribution and installs everything in
|
|
||||||
the right order.
|
|
||||||
|
|
||||||
## Manual fix — three commands (if you prefer)
|
|
||||||
|
|
||||||
### Linux Mint / Ubuntu / Debian / Pop!_OS
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Install the Qt6 Python binding
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y python3-pyqt6
|
|
||||||
|
|
||||||
# 2. Upgrade vaptvupt CLI to 5.0.0
|
|
||||||
sudo dpkg -i vaptvupt_5.0.0_amd64.deb
|
|
||||||
|
|
||||||
# 3. Install the GUI
|
|
||||||
sudo dpkg -i vaptvupt-gui_1.3.0_all.deb
|
|
||||||
```
|
|
||||||
|
|
||||||
If step 3 still complains about deps, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt --fix-broken install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fedora / RHEL / Rocky / AlmaLinux
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo dnf install -y python3-pyqt6
|
|
||||||
sudo dnf install -y vaptvupt-5.0.0-1.x86_64.rpm vaptvupt-gui-1.3.0-1.noarch.rpm
|
|
||||||
```
|
|
||||||
|
|
||||||
(Or build the RPM from the SRPM tarball with `rpmbuild -bb SPECS/vaptvupt.spec`)
|
|
||||||
|
|
||||||
### openSUSE Leap / Tumbleweed
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo zypper install python3-pyqt6
|
|
||||||
# Build the RPM from the source tarball — see the SRPM .tar.gz
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arch Linux / Manjaro / EndeavourOS
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo pacman -S python-pyqt6
|
|
||||||
# Build vaptvupt from the source tarball
|
|
||||||
```
|
|
||||||
|
|
||||||
### Anything else (or no apt/dnf/pacman handy)
|
|
||||||
|
|
||||||
Use the AppImage — no install needed:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
tar xzf VaptVupt-GUI-1.3.0-x86_64.AppDir.tar.gz
|
|
||||||
cd vaptvupt-gui.AppDir
|
|
||||||
./AppRun
|
|
||||||
```
|
|
||||||
|
|
||||||
The AppImage still needs Python 3 + Qt6 binding on the host. For a
|
|
||||||
fully standalone executable with no Python dependency, use a future
|
|
||||||
PyInstaller-built version (not in this release).
|
|
||||||
|
|
||||||
## Why does the GUI need Qt6?
|
|
||||||
|
|
||||||
The VaptVupt GUI is written in Python, using either PyQt6 or PySide6 (it
|
|
||||||
auto-detects whichever is installed). These are bindings to the Qt 6
|
|
||||||
graphical toolkit — they're how the GUI draws windows, buttons, and
|
|
||||||
dialogs.
|
|
||||||
|
|
||||||
PyQt6 is in the default repositories of major Linux distributions, so
|
|
||||||
installing it is one apt/dnf/zypper/pacman command away. We don't bundle
|
|
||||||
Qt6 inside the deb because:
|
|
||||||
|
|
||||||
- It's already on most modern systems
|
|
||||||
- Bundling would make the deb 80 MB+ instead of 35 KB
|
|
||||||
- Distribution-managed Qt gets security updates automatically
|
|
||||||
|
|
||||||
## Why does the GUI need vaptvupt 5.0.0?
|
|
||||||
|
|
||||||
The GUI calls `vaptvupt --pq` and `vaptvupt keygen` for native
|
|
||||||
post-quantum encryption (ML-KEM-768 + X25519, in-tree implementation).
|
|
||||||
Older CLI versions lack these flags, so the GUI's compress/extract will
|
|
||||||
fail against them.
|
|
||||||
|
|
||||||
## After installing — verify
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vaptvupt version # should show: 5.0.0
|
|
||||||
vaptvupt-gui # should launch the GUI window
|
|
||||||
```
|
|
||||||
|
|
||||||
## If the GUI window still doesn't appear
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run from terminal to see error messages
|
|
||||||
vaptvupt-gui
|
|
||||||
|
|
||||||
# If you see "ImportError: No module named 'PyQt6'":
|
|
||||||
# The GUI fell back through both PyQt6 and PySide6 imports.
|
|
||||||
# Re-check: python3 -c 'import PyQt6.QtWidgets'
|
|
||||||
|
|
||||||
# If you see "DISPLAY not set":
|
|
||||||
# You're on SSH without X forwarding. Use ssh -X or run locally.
|
|
||||||
|
|
||||||
# If you see "qt.qpa.plugin: Could not load the Qt platform plugin":
|
|
||||||
# Missing Qt platform plugin. On Mint/Ubuntu:
|
|
||||||
# sudo apt install qt6-qpa-plugins
|
|
||||||
```
|
|
||||||
|
|
||||||
## Reporting issues
|
|
||||||
|
|
||||||
If you've tried the above and vaptvupt-gui still won't work, open an issue
|
|
||||||
at https://git.securityops.co/cristiancmoises/vaptvupt/issues with:
|
|
||||||
|
|
||||||
1. Output of `lsb_release -a` (or `cat /etc/os-release`)
|
|
||||||
2. Output of `python3 --version`
|
|
||||||
3. Output of `python3 -c 'import PyQt6; print(PyQt6.__version__)' 2>&1`
|
|
||||||
4. Output of `vaptvupt version`
|
|
||||||
5. Output of `vaptvupt-gui` (the error message it printed to terminal)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Building from source
|
|
||||||
|
|
||||||
If you want to build VaptVupt from the source tarball instead of installing
|
|
||||||
the pre-built `.deb` / `.rpm` packages, you'll need only a C compiler and
|
|
||||||
make. The default build has NO external crypto dependency and installs no
|
|
||||||
shared library.
|
|
||||||
|
|
||||||
### Build dependencies (default build)
|
|
||||||
|
|
||||||
| Component | Why needed |
|
|
||||||
|---|---|
|
|---|---|
|
||||||
| `gcc` ≥ 7 or `clang` ≥ 10 | C11 compiler |
|
| CLI | `zupt-5.2.2.tar.gz`, `zupt_5.2.2_amd64.deb`, openSUSE x86_64 binary/source RPMs, `zupt-5.2.2-linux-x86_64.tar.xz`, `zupt-5.2.2-windows-x86_64.zip`, and `ZUPT-5.2.2-macOS-*.dmg` |
|
||||||
| `make` | build driver |
|
| GUI | `zupt-gui_5.2.2_all.deb`, `zupt-gui-5.2.2-1.noarch.rpm`, `zupt-gui-5.2.2-1.src.rpm`, and `zupt-gui-5.2.2-portable.zip` |
|
||||||
| libm, pthread | math and threading (part of the standard C library/toolchain) |
|
|
||||||
|
|
||||||
The default build uses PBKDF2-SHA256 (600k iterations) for password KDF
|
The GUI packages require the matching `zupt` CLI package and must pass exact
|
||||||
and the in-tree native `--pq` mode (ML-KEM-768 + X25519) for post-quantum
|
payload/dependency checks plus an installed off-screen GUI/CLI integration
|
||||||
encryption. No `libzuptsdk`, no OpenSSL, no libargon2 is required.
|
test. The source-only portable GUI ZIP bundles launchers, notices, and GUI
|
||||||
|
source, but not Python, Qt, or the CLI. The Linux tar.xz carries the tested CLI
|
||||||
|
beside the complete public license/notice payload. AppImage, AppDir, Flatpak
|
||||||
|
bundles, GUI platform installers, and bare Linux/Windows executables are not
|
||||||
|
promoted for 5.2.2. The Windows ZIP and macOS DMG contain the CLI only. Exact
|
||||||
|
target boundaries are listed in `README.md`.
|
||||||
|
The release's `SHA256SUMS` and validation notes, not the mere presence of a
|
||||||
|
download link, identify an artifact that completed its gate.
|
||||||
|
|
||||||
### Install build dependencies (Debian/Ubuntu/Mint)
|
Do not install a package for a different distribution or CPU architecture.
|
||||||
|
|
||||||
```bash
|
## Build requirements
|
||||||
sudo apt install build-essential
|
|
||||||
|
The default CLI build requires:
|
||||||
|
|
||||||
|
- a C11 compiler;
|
||||||
|
- GNU make;
|
||||||
|
- the platform C library, math library, and threading support;
|
||||||
|
- standard build utilities including `gzip` for installation and source export.
|
||||||
|
|
||||||
|
It does not need a vendored binary, OpenSSL, libargon2, `libvuptsdk`, or
|
||||||
|
`libpqvaptvupt`. Dependencies must be installed before the build; `make` does
|
||||||
|
not download anything.
|
||||||
|
|
||||||
|
Typical package-manager commands are:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Debian / Ubuntu
|
||||||
|
sudo apt install build-essential gzip
|
||||||
|
|
||||||
|
# Fedora / RHEL family
|
||||||
|
sudo dnf install gcc make gzip
|
||||||
|
|
||||||
|
# openSUSE
|
||||||
|
sudo zypper install gcc make gzip
|
||||||
|
|
||||||
|
# Arch Linux
|
||||||
|
sudo pacman -S base-devel gzip
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install build dependencies (Fedora/RHEL/openSUSE)
|
Package names can differ by distribution release. These commands are examples,
|
||||||
|
not a statement that 5.2.2 has been accepted into each distribution repository.
|
||||||
|
|
||||||
```bash
|
## Build and test from source
|
||||||
sudo dnf install gcc make # Fedora/RHEL
|
|
||||||
sudo zypper install gcc make # openSUSE
|
Verify the checkout or extracted archive, then use the source-only feature set:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scripts/check-source-only.sh
|
||||||
|
make clean
|
||||||
|
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 1)" \
|
||||||
|
WITH_SDK=0 WITH_PQBOX=0 V=1
|
||||||
|
make WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
|
./zupt --version
|
||||||
|
./zupt --help
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build VaptVupt itself
|
From a release archive, run the scanner as follows before extraction or from a
|
||||||
|
trusted checkout after download:
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
tar -xzf vaptvupt-5.0.0-source.tar.gz
|
scripts/check-source-only.sh --archive /path/to/zupt-5.2.2.tar.gz
|
||||||
cd vaptvupt-5.0.0
|
|
||||||
|
|
||||||
make # build the `./vaptvupt` binary
|
|
||||||
sudo make install # install to /usr/local/bin (override with PREFIX=/usr)
|
|
||||||
|
|
||||||
./vaptvupt version # verify
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `make` step takes 10-30 seconds. The build emits the binary as
|
The default build provides the native password, ML-KEM-768 + X25519 hybrid
|
||||||
`./vaptvupt`. The default install prefix is `/usr/local`; override with
|
`--pq`, and ML-KEM-768 `--pq-only` paths. See `SECURITY.md` and
|
||||||
`PREFIX=/usr` for system-wide install.
|
`THREAT_MODEL.md` before selecting an encryption mode.
|
||||||
|
|
||||||
### Run the test suite
|
For password encryption, prefer one of the explicit non-argv inputs:
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
make test
|
# Interactive, without terminal echo; compress confirms the password.
|
||||||
|
zupt compress --password-prompt backup.zupt files/
|
||||||
|
|
||||||
|
# Read the first line of a mode-0600 file.
|
||||||
|
zupt test --pass-file /secure/path/password.txt backup.zupt
|
||||||
|
|
||||||
|
# Read the first line from an inherited descriptor.
|
||||||
|
zupt extract --pass-fd 3 -o restored backup.zupt 3</secure/path/password.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
Covers roundtrip, multi-file, cross-block, dedup property, path-traversal,
|
`-p/--password PASSWORD` remains available for compatibility, but the password
|
||||||
argument-order, and block-swap regression. Each suite reports its own
|
can be visible in shell history and process listings. `--pass-file` and
|
||||||
pass/fail count.
|
`--pass-fd` reject empty, NUL-containing, or overlong input and remove the
|
||||||
|
line-ending delimiter.
|
||||||
|
|
||||||
### Cross-compilation
|
`make check` is the downstream-safe test gate. `make test-all` runs the broader
|
||||||
|
upstream suite. Optional tests remain conditional on their corresponding
|
||||||
|
system-built dependencies and must be reported as skipped when unavailable.
|
||||||
|
|
||||||
VaptVupt builds on x86_64, aarch64, armhf, ppc64le, s390x, and riscv64. To
|
## Install
|
||||||
cross-compile:
|
|
||||||
|
|
||||||
```bash
|
The upstream default prefix is `/usr/local`:
|
||||||
make CC=aarch64-linux-gnu-gcc # for AArch64
|
|
||||||
make CC=arm-linux-gnueabihf-gcc # for ARMv7 (Raspberry Pi 32-bit)
|
```sh
|
||||||
|
sudo make WITH_SDK=0 WITH_PQBOX=0 install
|
||||||
|
zupt --version
|
||||||
```
|
```
|
||||||
|
|
||||||
The Makefile auto-detects target architecture via `$(CC) -dumpmachine`
|
For a distribution-style `/usr` installation, or when building a package:
|
||||||
and selects the appropriate SIMD flags (NEON on AArch64, SSE4/AVX2 on
|
|
||||||
x86_64).
|
|
||||||
|
|
||||||
### Optional: WITH_SDK=1 build
|
```sh
|
||||||
|
make DESTDIR="$pkgroot" PREFIX=/usr \
|
||||||
The SDK-backed modes — `--pq-sdk`, `--pq-box` (sealed-box), and the
|
WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 install
|
||||||
Argon2id KDF — are optional. They are not in the default build and require
|
|
||||||
building against the separately distributed `libzuptsdk` / `libpqvaptvupt`
|
|
||||||
libraries:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make WITH_SDK=1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This build additionally needs the SDK development package and its runtime
|
`DESTDIR` stages the files below a package root; it is not embedded in installed
|
||||||
dependencies (OpenSSL libcrypto, libargon2), which ship with the SDK
|
paths. `PREFIX`, `BINDIR`, `LIBDIR`, `INCLUDEDIR`, and `MANDIR` can be overridden
|
||||||
distribution. Without `WITH_SDK=1`, the `--pq-sdk` and `--pq-box` flags are
|
without replacing packager-supplied compiler or linker flags.
|
||||||
unavailable; use the native `--pq` mode instead.
|
|
||||||
|
The default installation provides `zupt`. To install the `vaptvupt` command
|
||||||
|
and manual-page compatibility aliases for versions 3.0.0 through 5.2.1, use:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo make INSTALL_LEGACY_ALIAS=1 install
|
||||||
|
```
|
||||||
|
|
||||||
|
The openSUSE main package installs `/usr/bin/zupt` as the primary command and
|
||||||
|
does not need the optional compatibility alias.
|
||||||
|
|
||||||
|
To remove an installation made with the same prefix:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo make PREFIX=/usr/local uninstall
|
||||||
|
```
|
||||||
|
|
||||||
|
## Optional system integrations
|
||||||
|
|
||||||
|
The SDK and PQBOX integrations are independent and disabled by default:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Requires a system libvuptsdk development package or explicit SDK_* flags
|
||||||
|
make WITH_SDK=1 WITH_PQBOX=0
|
||||||
|
|
||||||
|
# Requires a system libpqvaptvupt development package or explicit PQBOX_* flags
|
||||||
|
make WITH_SDK=0 WITH_PQBOX=1
|
||||||
|
|
||||||
|
# Enable both only when both system dependencies are installed
|
||||||
|
make WITH_SDK=1 WITH_PQBOX=1
|
||||||
|
```
|
||||||
|
|
||||||
|
The Makefile normally obtains flags from `pkg-config`. A packager may provide
|
||||||
|
`SDK_CPPFLAGS`/`SDK_LDLIBS` or `PQBOX_CPPFLAGS`/`PQBOX_LDLIBS` explicitly. A
|
||||||
|
missing dependency is an error: there is no download and no fallback to a local
|
||||||
|
precompiled library.
|
||||||
|
|
||||||
|
Textual assembly under `jasmin/` can be selected separately with
|
||||||
|
`WITH_JASMIN=1` on a supported x86_64 compiler target. The directory contains
|
||||||
|
Jasmin-generated outputs and a separately identified hand-written production
|
||||||
|
unit; it is off by default and the portable C implementations are the baseline
|
||||||
|
build. Do not infer that an architecture is supported until that target has
|
||||||
|
actually built and passed its tests.
|
||||||
|
|
||||||
|
## GUI
|
||||||
|
|
||||||
|
The GUI invokes the `zupt` CLI; it does not replace the CLI or implement
|
||||||
|
archive cryptography in Python. Install and verify the CLI first:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
zupt --version
|
||||||
|
python3 -m venv ~/.local/share/zupt-gui-venv
|
||||||
|
~/.local/share/zupt-gui-venv/bin/pip install PySide6
|
||||||
|
~/.local/share/zupt-gui-venv/bin/python gui/src/zupt_gui.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The GUI can use PySide6 or PyQt6. Prefer a distribution-managed Qt binding when
|
||||||
|
available. A package-specific installer may provide launchers and desktop
|
||||||
|
integration; consult its release notes instead of assuming a particular GUI
|
||||||
|
package version or filename.
|
||||||
|
|
||||||
|
For a headless sanity check:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 gui/src/zupt_gui.py --version
|
||||||
|
python3 gui/src/zupt_gui.py --selftest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If the CLI is not found, inspect the selected prefix:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
command -v zupt
|
||||||
|
printf '%s\n' "$PATH"
|
||||||
|
```
|
||||||
|
|
||||||
|
If the GUI cannot find it, install the CLI in a directory on `PATH` or set
|
||||||
|
`ZUPT_BIN` to its absolute path. `VAPTVUPT_BIN` remains a compatibility
|
||||||
|
fallback. For Qt import failures, verify the same
|
||||||
|
Python interpreter that starts the GUI:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 -c 'import PySide6.QtWidgets'
|
||||||
|
```
|
||||||
|
|
||||||
|
For build failures, rerun with `V=1` and include the compiler target, full build
|
||||||
|
command, and first error in the issue report. Do not attach credentials,
|
||||||
|
private keys, passwords, or sensitive archives.
|
||||||
|
|
||||||
|
Report issues at:
|
||||||
|
`https://github.com/cristiancmoises/zupt/issues`.
|
||||||
|
|
|
||||||
121
LICENSE
121
LICENSE
|
|
@ -1,92 +1,65 @@
|
||||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
ZUPT licensing summary
|
||||||
Version 3, 19 November 2007
|
======================
|
||||||
|
|
||||||
Copyright (C) 2026 Cristian Cezar Moisés <zupt@riseup.net>
|
Copyright (C) 2025-2026 Cristian Cezar Moisés
|
||||||
|
|
||||||
VaptVupt (formerly Zupt; renamed in v3.0.0 due to a prior INPI Brasil
|
The ZUPT application, command-line interface, graphical interface,
|
||||||
trademark registration of "Zupt" for unrelated software) is free
|
cryptographic tool code, build files, and documentation identified by the
|
||||||
software: you can redistribute it and/or modify it under the terms of
|
following SPDX expression are free software under:
|
||||||
the GNU Affero General Public License as published by the Free
|
|
||||||
Software Foundation, either version 3 of the License, or (at your
|
|
||||||
option) any later version.
|
|
||||||
|
|
||||||
VaptVupt is distributed in the hope that it will be useful, but
|
AGPL-3.0-or-later
|
||||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public
|
The integrated VaptVupt compression codec is a separately identified component.
|
||||||
License along with this program. If not, see:
|
The codec files carry this SPDX expression:
|
||||||
|
|
||||||
https://www.gnu.org/licenses/agpl-3.0.txt
|
GPL-3.0-or-later
|
||||||
https://www.gnu.org/licenses/agpl-3.0.html
|
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
The two source files derived from Yann Collet's xxHash implementation carry an
|
||||||
|
additional BSD-2-Clause obligation:
|
||||||
|
|
||||||
─────────────────────────────────────────────────────────────────────
|
src/zupt_xxh.c
|
||||||
|
src/vv_xxh64.c
|
||||||
|
|
||||||
ABOUT THIS LICENSE
|
Portions of the native ML-KEM implementation were adapted from the
|
||||||
|
pq-crystals/kyber reference implementation. Upstream offers that code under
|
||||||
|
CC0-1.0 or Apache-2.0; this distribution uses the CC0-1.0 option for those
|
||||||
|
portions:
|
||||||
|
|
||||||
The GNU Affero General Public License v3 (AGPLv3) is a copyleft
|
src/zupt_mlkem.c
|
||||||
license designed for software that may be run as a network service.
|
|
||||||
It is identical to the GNU General Public License v3, with one
|
|
||||||
additional requirement (Section 13): if you modify libzuptsdk and
|
|
||||||
make the modified version available to users over a computer network,
|
|
||||||
you must offer those users access to the corresponding modified
|
|
||||||
source code.
|
|
||||||
|
|
||||||
This protects Zupt against being adopted by SaaS providers as
|
Portions of the native X25519 implementation were adapted from
|
||||||
a private fork without contributing back, while keeping it freely
|
curve25519-donna and conservatively retain its repository BSD-3-Clause terms:
|
||||||
usable by individuals, small businesses, and the broader open-source
|
|
||||||
community.
|
|
||||||
|
|
||||||
If you write a separate program that is distributed alongside
|
src/zupt_x25519.c
|
||||||
Zupt (for example, statically linking it into your own
|
|
||||||
application), the AGPL requires you to license that combined work
|
|
||||||
under the AGPL as well — which means you must publish the source.
|
|
||||||
If this is not acceptable for your use case, please contact the
|
|
||||||
author for commercial licensing options:
|
|
||||||
|
|
||||||
sac@securityops.co
|
The codec scope consists of src/vv_*.c, src/vaptvupt_api.c,
|
||||||
https://git.securityops.co/cristiancmoises/zupt
|
include/vaptvupt*.h, and include/vv_*.h. Per-file SPDX notices are
|
||||||
|
authoritative if a file falls outside this summary.
|
||||||
|
|
||||||
─────────────────────────────────────────────────────────────────────
|
GPL-3.0-or-later and AGPL-3.0-or-later code may be combined under section 13 of
|
||||||
|
the licenses. Distribution of this repository therefore needs to preserve both
|
||||||
|
license scopes and their notices. The unmodified license texts are provided in:
|
||||||
|
|
||||||
The full text of the GNU Affero General Public License version 3
|
LICENSE-AGPL-3.0
|
||||||
should accompany this distribution as a separate file (or you may
|
LICENSE-GPL-3.0
|
||||||
download it from the URLs above). It is approximately 35 KB / 619
|
LICENSE-BSD-2-Clause
|
||||||
lines of plain text.
|
LICENSE-BSD-3-Clause
|
||||||
|
LICENSE-CC0-1.0
|
||||||
|
|
||||||
─────────────────────────────────────────────────────────────────────
|
ZUPT is distributed without warranty; see the applicable license text for
|
||||||
|
the complete terms.
|
||||||
|
|
||||||
NOTE ON VAPTVUPT (GPL, NOT AGPL)
|
Historical licensing note: published repository history includes earlier
|
||||||
|
first-party application and GUI material distributed with MIT license notices.
|
||||||
|
Those permissions remain applicable to the exact material distributed under
|
||||||
|
them; the current license summary does not revoke or reinterpret an earlier
|
||||||
|
grant. The 5.2.2 erratum in CHANGELOG.md identifies the known repository
|
||||||
|
evidence. Current files follow their current per-file SPDX notices.
|
||||||
|
|
||||||
The VaptVupt LZ + tANS codec, located in:
|
The applicable copyright holder may separately offer commercial terms for
|
||||||
|
first-party rights that the holder controls. LICENSE-COMMERCIAL is only a
|
||||||
|
licensing inquiry and scope notice; it is not a commercial license grant and
|
||||||
|
does not relicense third-party or separately noticed material.
|
||||||
|
|
||||||
src/vv_*.c
|
Commercial licensing contact: sac@securityops.co
|
||||||
src/vaptvupt_api.c
|
Canonical repository: https://github.com/cristiancmoises/zupt
|
||||||
include/vaptvupt*.h
|
|
||||||
include/vv_*.h
|
|
||||||
|
|
||||||
is licensed under the GNU General Public License version 3 or later
|
|
||||||
(GPL-3.0-or-later), NOT the AGPL. This deliberate licensing choice is
|
|
||||||
made so that, with sufficient maturity, VaptVupt can be considered
|
|
||||||
for upstreaming into the Linux or BSD kernels (which require GPL-
|
|
||||||
compatible licensing).
|
|
||||||
|
|
||||||
The standalone repository for VaptVupt is at:
|
|
||||||
|
|
||||||
https://git.securityops.co/cristiancmoises/vaptvupt
|
|
||||||
|
|
||||||
The combination of GPL-licensed VaptVupt with AGPL-licensed Zupt is
|
|
||||||
explicitly intended by the author and consistent with the rights
|
|
||||||
retained by sole-authorship.
|
|
||||||
|
|
||||||
─────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
COMMERCIAL LICENSING
|
|
||||||
|
|
||||||
Both AGPL and GPL components may be commercially relicensed by the
|
|
||||||
author. If you require relief from copyleft terms, contact:
|
|
||||||
|
|
||||||
sac@securityops.co
|
|
||||||
|
|
|
||||||
26
LICENSE-BSD-2-Clause
Normal file
26
LICENSE-BSD-2-Clause
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
xxHash Library
|
||||||
|
Copyright (c) 2012-2021 Yann Collet
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer in the documentation and/or
|
||||||
|
other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
46
LICENSE-BSD-3-Clause
Normal file
46
LICENSE-BSD-3-Clause
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
Copyright 2008, Google Inc.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
curve25519-donna: Curve25519 elliptic curve, public key function
|
||||||
|
|
||||||
|
http://code.google.com/p/curve25519-donna/
|
||||||
|
|
||||||
|
Adam Langley <agl@imperialviolet.org>
|
||||||
|
|
||||||
|
Derived from public domain C code by Daniel J. Bernstein <djb@cr.yp.to>
|
||||||
|
|
||||||
|
More information about curve25519 can be found here
|
||||||
|
http://cr.yp.to/ecdh.html
|
||||||
|
|
||||||
|
djb's sample implementation of curve25519 is written in a special assembly
|
||||||
|
language called qhasm and uses the floating point registers.
|
||||||
|
|
||||||
|
This is, almost, a clean room reimplementation from the curve25519 paper. It
|
||||||
|
uses many of the tricks described therein. Only the crecip function is taken
|
||||||
|
from the sample implementation.
|
||||||
121
LICENSE-CC0-1.0
Normal file
121
LICENSE-CC0-1.0
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
Creative Commons Legal Code
|
||||||
|
|
||||||
|
CC0 1.0 Universal
|
||||||
|
|
||||||
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||||
|
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||||
|
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||||
|
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||||
|
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||||
|
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||||
|
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||||
|
HEREUNDER.
|
||||||
|
|
||||||
|
Statement of Purpose
|
||||||
|
|
||||||
|
The laws of most jurisdictions throughout the world automatically confer
|
||||||
|
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||||
|
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||||
|
authorship and/or a database (each, a "Work").
|
||||||
|
|
||||||
|
Certain owners wish to permanently relinquish those rights to a Work for
|
||||||
|
the purpose of contributing to a commons of creative, cultural and
|
||||||
|
scientific works ("Commons") that the public can reliably and without fear
|
||||||
|
of later claims of infringement build upon, modify, incorporate in other
|
||||||
|
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||||
|
and for any purposes, including without limitation commercial purposes.
|
||||||
|
These owners may contribute to the Commons to promote the ideal of a free
|
||||||
|
culture and the further production of creative, cultural and scientific
|
||||||
|
works, or to gain reputation or greater distribution for their Work in
|
||||||
|
part through the use and efforts of others.
|
||||||
|
|
||||||
|
For these and/or other purposes and motivations, and without any
|
||||||
|
expectation of additional consideration or compensation, the person
|
||||||
|
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||||
|
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||||
|
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||||
|
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||||
|
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||||
|
|
||||||
|
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||||
|
protected by copyright and related or neighboring rights ("Copyright and
|
||||||
|
Related Rights"). Copyright and Related Rights include, but are not
|
||||||
|
limited to, the following:
|
||||||
|
|
||||||
|
i. the right to reproduce, adapt, distribute, perform, display,
|
||||||
|
communicate, and translate a Work;
|
||||||
|
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||||
|
iii. publicity and privacy rights pertaining to a person's image or
|
||||||
|
likeness depicted in a Work;
|
||||||
|
iv. rights protecting against unfair competition in regards to a Work,
|
||||||
|
subject to the limitations in paragraph 4(a), below;
|
||||||
|
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||||
|
in a Work;
|
||||||
|
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||||
|
European Parliament and of the Council of 11 March 1996 on the legal
|
||||||
|
protection of databases, and under any national implementation
|
||||||
|
thereof, including any amended or successor version of such
|
||||||
|
directive); and
|
||||||
|
vii. other similar, equivalent or corresponding rights throughout the
|
||||||
|
world based on applicable law or treaty, and any national
|
||||||
|
implementations thereof.
|
||||||
|
|
||||||
|
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||||
|
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||||
|
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||||
|
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||||
|
of action, whether now known or unknown (including existing as well as
|
||||||
|
future claims and causes of action), in the Work (i) in all territories
|
||||||
|
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||||
|
treaty (including future time extensions), (iii) in any current or future
|
||||||
|
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||||
|
including without limitation commercial, advertising or promotional
|
||||||
|
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||||
|
member of the public at large and to the detriment of Affirmer's heirs and
|
||||||
|
successors, fully intending that such Waiver shall not be subject to
|
||||||
|
revocation, rescission, cancellation, termination, or any other legal or
|
||||||
|
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||||
|
as contemplated by Affirmer's express Statement of Purpose.
|
||||||
|
|
||||||
|
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||||
|
be judged legally invalid or ineffective under applicable law, then the
|
||||||
|
Waiver shall be preserved to the maximum extent permitted taking into
|
||||||
|
account Affirmer's express Statement of Purpose. In addition, to the
|
||||||
|
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||||
|
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||||
|
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||||
|
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||||
|
maximum duration provided by applicable law or treaty (including future
|
||||||
|
time extensions), (iii) in any current or future medium and for any number
|
||||||
|
of copies, and (iv) for any purpose whatsoever, including without
|
||||||
|
limitation commercial, advertising or promotional purposes (the
|
||||||
|
"License"). The License shall be deemed effective as of the date CC0 was
|
||||||
|
applied by Affirmer to the Work. Should any part of the License for any
|
||||||
|
reason be judged legally invalid or ineffective under applicable law, such
|
||||||
|
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||||
|
of the License, and in such case Affirmer hereby affirms that he or she
|
||||||
|
will not (i) exercise any of his or her remaining Copyright and Related
|
||||||
|
Rights in the Work or (ii) assert any associated claims and causes of
|
||||||
|
action with respect to the Work, in either case contrary to Affirmer's
|
||||||
|
express Statement of Purpose.
|
||||||
|
|
||||||
|
4. Limitations and Disclaimers.
|
||||||
|
|
||||||
|
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||||
|
surrendered, licensed or otherwise affected by this document.
|
||||||
|
b. Affirmer offers the Work as-is and makes no representations or
|
||||||
|
warranties of any kind concerning the Work, express, implied,
|
||||||
|
statutory or otherwise, including without limitation warranties of
|
||||||
|
title, merchantability, fitness for a particular purpose, non
|
||||||
|
infringement, or the absence of latent or other defects, accuracy, or
|
||||||
|
the present or absence of errors, whether or not discoverable, all to
|
||||||
|
the greatest extent permissible under applicable law.
|
||||||
|
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||||
|
that may apply to the Work or any use thereof, including without
|
||||||
|
limitation any person's Copyright and Related Rights in the Work.
|
||||||
|
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||||
|
consents, permissions or other rights required for any use of the
|
||||||
|
Work.
|
||||||
|
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||||
|
party to this document and has no duty or obligation with respect to
|
||||||
|
this CC0 or use of the Work.
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
VAPTVUPT COMMERCIAL LICENSING NOTICE
|
ZUPT COMMERCIAL LICENSING NOTICE
|
||||||
|
|
||||||
First-party VaptVupt code is publicly licensed under the per-file terms:
|
First-party ZUPT code is publicly licensed under the per-file terms:
|
||||||
AGPL-3.0-or-later for the application/GUI/cryptographic tool code and
|
AGPL-3.0-or-later for the application/GUI/cryptographic tool code and
|
||||||
GPL-3.0-or-later for the separately identified compression codec code.
|
GPL-3.0-or-later for the separately identified VaptVupt compression codec.
|
||||||
|
|
||||||
The applicable copyright holder may also offer those first-party rights under
|
The applicable copyright holder may also offer those first-party rights under
|
||||||
a separate written commercial agreement executed with the licensee.
|
a separate written commercial agreement executed with the licensee.
|
||||||
|
|
|
||||||
674
LICENSE-GPL-3.0
Normal file
674
LICENSE-GPL-3.0
Normal file
|
|
@ -0,0 +1,674 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
47
NOTICE
47
NOTICE
|
|
@ -1,22 +1,39 @@
|
||||||
VaptVupt licensing notice
|
ZUPT notices
|
||||||
=========================
|
============
|
||||||
|
|
||||||
Copyright in first-party code remains with the copyright holders identified in
|
Copyright remains with the holders identified by per-file notices and repository
|
||||||
file notices and repository history.
|
history.
|
||||||
|
|
||||||
VaptVupt intentionally has two public source-license scopes:
|
Public source-license scopes:
|
||||||
|
|
||||||
- AGPL-3.0-or-later for application, GUI, SDK integration, and cryptographic
|
- application, GUI, cryptographic tool, build and documentation code:
|
||||||
tool code identified by that SPDX expression;
|
AGPL-3.0-or-later;
|
||||||
- GPL-3.0-or-later for the VaptVupt compression codec files identified by that
|
- integrated VaptVupt compression codec files identified in LICENSE:
|
||||||
SPDX expression.
|
GPL-3.0-or-later.
|
||||||
|
- xxHash-derived routines in `src/zupt_xxh.c` and `src/vv_xxh64.c`:
|
||||||
|
BSD-2-Clause in addition to their application/codec license.
|
||||||
|
- pq-crystals/kyber-derived portions in `src/zupt_mlkem.c`:
|
||||||
|
CC0-1.0 in addition to the application license.
|
||||||
|
- curve25519-donna-derived portions in `src/zupt_x25519.c`:
|
||||||
|
BSD-3-Clause in addition to the application license; Copyright 2008,
|
||||||
|
Google Inc.
|
||||||
|
- the x86 BCJ state machine in `src/vv_bcj.c` is adapted from Igor Pavlov's
|
||||||
|
public-domain LZMA SDK source.
|
||||||
|
- the SHA-NI compression path in `src/zupt_sha256_shani.c` is adapted from
|
||||||
|
Jeffrey Walton's public-domain SHA-Intrinsics reference.
|
||||||
|
|
||||||
The unmodified public license texts are in LICENSE-AGPL-3.0 and the applicable
|
The corresponding unmodified texts are LICENSE-AGPL-3.0,
|
||||||
GPL files/notices. The copyright holder may separately license controlled
|
LICENSE-GPL-3.0, LICENSE-BSD-2-Clause, LICENSE-BSD-3-Clause, and
|
||||||
first-party rights through an individually executed commercial agreement.
|
LICENSE-CC0-1.0. Preserve THIRD-PARTY-NOTICES.md and all per-file SPDX and
|
||||||
LICENSE-COMMERCIAL is an inquiry and scope notice, not such an agreement.
|
copyright notices when redistributing the source.
|
||||||
|
|
||||||
No option relabels vendored or third-party material. Per-file notices and
|
Published historical revisions contain MIT notices for some first-party
|
||||||
THIRD-PARTY-NOTICES.md control component scope.
|
application and GUI material. Those historical permissions remain attached to
|
||||||
|
the exact material distributed under them; see the 5.2.2 licensing erratum in
|
||||||
|
CHANGELOG.md. The current source scopes above do not revoke an earlier grant.
|
||||||
|
|
||||||
|
LICENSE-COMMERCIAL describes a possible separately executed commercial
|
||||||
|
agreement for controlled first-party rights. It grants no additional permission
|
||||||
|
by itself and does not alter the public licenses.
|
||||||
|
|
||||||
Commercial licensing contact: sac@securityops.co
|
Commercial licensing contact: sac@securityops.co
|
||||||
|
|
|
||||||
555
SECURITY.md
555
SECURITY.md
|
|
@ -1,326 +1,313 @@
|
||||||
# Security Policy — VaptVupt 5.0.0
|
# Security Policy — ZUPT 5.2.2
|
||||||
|
|
||||||
## Reporting Vulnerabilities
|
## Reporting vulnerabilities
|
||||||
|
|
||||||
Report privately by email to **zupt@riseup.net** with `[security]` in the
|
Report suspected vulnerabilities privately to **zupt@riseup.net** with
|
||||||
subject. Do not open a public issue on the project's git server.
|
`[security]` in the subject. Do not open a public issue before coordinated
|
||||||
|
disclosure.
|
||||||
|
|
||||||
Include:
|
Include the output of `zupt --version`, operating system and architecture,
|
||||||
|
impact, and the smallest safe reproducer. Remove passwords, keys, tokens,
|
||||||
|
personal data, and confidential archive contents.
|
||||||
|
|
||||||
- Version (`vaptvupt --version`) and platform.
|
The project aims to acknowledge reports within five business days and to target
|
||||||
- Description, impact assessment, and a reproduction (a minimal archive or
|
high-severity fixes within 30 days, with the disclosure timeline agreed case by
|
||||||
a code snippet).
|
case. These are targets, not a warranty.
|
||||||
|
|
||||||
Disclosure SLA: acknowledgement within 5 business days; target fix within
|
ZUPT has not had an independent third-party security audit or certification.
|
||||||
30 days for high-severity issues. Coordinated disclosure preferred; the
|
Treat the in-repository review and tests as reproducible project evidence, not
|
||||||
timeline is discussed case by case. A PGP key is on the project's
|
as external assurance.
|
||||||
keyserver entry.
|
|
||||||
|
|
||||||
The project has not had an external independent audit. For high-stakes
|
## Supported security modes
|
||||||
deployments, treat it as "reviewed but unaudited" and do your own review.
|
|
||||||
|
|
||||||
---
|
| Mode | CLI | Key establishment / derivation | Payload protection |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Plain | no encryption option | none | compression checksums only |
|
||||||
|
| Password | `-p/--password`, `--password-prompt`, `--pass-file`, or `--pass-fd` | PBKDF2-SHA256, 600,000 iterations | AES-256-CTR + HMAC-SHA256 |
|
||||||
|
| Native hybrid PQ | `--pq` | ML-KEM-768 + X25519, SHA3-512 combiner | AES-256-CTR + HMAC-SHA256 |
|
||||||
|
| Native PQ only | `--pq-only` | ML-KEM-768, SHA3-512 derivation | AES-256-CTR + HMAC-SHA256 |
|
||||||
|
|
||||||
## Encryption Modes
|
The native hybrid mode is the recommended post-quantum mode unless a policy
|
||||||
|
forbids a classical component. `--pq-only` removes the X25519 fallback: a break
|
||||||
|
of ML-KEM-768 alone would then compromise key establishment. Password security
|
||||||
|
is bounded by password entropy; PBKDF2 cannot make a short or reused password
|
||||||
|
safe against offline guessing.
|
||||||
|
|
||||||
| Mode | CLI Flag | Algorithm | PQ-Safe? | Use Case |
|
The `-p/--password PASSWORD` argument form can be visible to process-list users
|
||||||
|------|----------|-----------|----------|----------|
|
and shell history. Prefer `--password-prompt`, `--pass-file` with restrictive
|
||||||
| Password | `-p` | PBKDF2-SHA256 → AES-256-CTR + HMAC-SHA256 | No | Short-term backups, personal use |
|
permissions, or `--pass-fd` with a descriptor inherited from a trusted caller.
|
||||||
| PQ Hybrid | `--pq` | ML-KEM-768 + X25519 → AES-256-CTR + HMAC-SHA256 | Yes | Long-term archives, high-value data (**recommended**) |
|
The file/descriptor forms read one line, remove LF and an optional preceding CR,
|
||||||
| PQ Only | `--pq-only` | ML-KEM-768 only → AES-256-CTR + HMAC-SHA256 | Yes | "PQ-only" compliance postures (no classical KEM) |
|
and reject empty, NUL-containing, or overlong input. ZUPT does not enforce
|
||||||
| None | (default) | No encryption (compression only) | N/A | Non-sensitive data |
|
password-file ownership or mode; the caller remains responsible for creating,
|
||||||
|
protecting, and deleting that file. The descriptor form duplicates the supplied
|
||||||
|
descriptor and does not close the caller's original descriptor.
|
||||||
|
The duplicate shares the same underlying stream and offset, and buffered input
|
||||||
|
may consume beyond the password line. Pass a descriptor dedicated to this one
|
||||||
|
password read; do not reuse it as a multi-record protocol channel.
|
||||||
|
|
||||||
Password mode (`-p`) is not quantum-safe. For protection against "harvest
|
On POSIX terminals, the explicit prompt saves terminal state and installs
|
||||||
now, decrypt later" quantum attacks, use `--pq` — the recommended
|
signal-aware cleanup so a handled interruption restores echo and other changed
|
||||||
post-quantum mode. `--pq` is native and in-tree; it needs no external
|
settings before termination. This behavior is covered by a PTY regression and
|
||||||
library.
|
must be rerun on the exact release candidate.
|
||||||
|
|
||||||
`--pq-only` (envelope type `0x06`) uses ML-KEM-768 as the *sole* key
|
## Native key files
|
||||||
mechanism, with no classical X25519 component. It exists for compliance
|
|
||||||
postures that mandate a single NIST-standardised PQ primitive with no
|
|
||||||
classical KEM in the envelope (CNSA 2.0-style "PQ-only"). **This is a
|
|
||||||
deliberate reduction in defence-in-depth:** unlike `--pq`, there is no
|
|
||||||
classical fallback, so a future cryptanalytic break of ML-KEM-768 alone is
|
|
||||||
sufficient to break the archive. Under `--pq`, an attacker must break *both*
|
|
||||||
ML-KEM-768 and X25519. **Unless a policy forbids the classical component,
|
|
||||||
prefer `--pq`.** Both modes are native, in-tree, and need no external
|
|
||||||
library.
|
|
||||||
|
|
||||||
Optional SDK modes (`--pq-sdk`, `--pq-box`) are available only in an
|
Native private keys use no-replace creation: POSIX files are mode `0600` and
|
||||||
upstream `make WITH_SDK=1` build linked against the separately distributed
|
Windows files receive a current-user-only DACL. An existing destination is
|
||||||
libzuptsdk / libpqvaptvupt libraries. They are not part of the default
|
never truncated. If write, flush/fsync, or close fails, ZUPT deliberately leaves
|
||||||
build and are not defaults.
|
the exclusively created incomplete or durability-uncertain file at that path
|
||||||
|
for the user to inspect and remove. It does not unlink by pathname after close,
|
||||||
|
which avoids deleting a replacement installed during a race. Public keys may be
|
||||||
|
shared deliberately and are not treated as secret. ZKEY and ZPQK readers
|
||||||
|
validate the checksum, format version, flags, reserved bytes, exact encoded
|
||||||
|
size, and public/private role before using any key material. A truncated,
|
||||||
|
extended, structurally invalid, or role-confused key is rejected rather than
|
||||||
|
partially accepted.
|
||||||
|
|
||||||
---
|
### Optional integrations
|
||||||
|
|
||||||
## Cryptographic Algorithms
|
The 5.2.2 default is `WITH_SDK=0 WITH_PQBOX=0`:
|
||||||
|
|
||||||
| Component | Algorithm | Standard | Key Size | Security Level |
|
- `WITH_SDK=1` enables libvuptsdk-backed features, including the SDK PQ mode
|
||||||
|-----------|-----------|----------|----------|---------------|
|
and Argon2id support, using a separately installed system development package.
|
||||||
| Symmetric encryption | AES-256-CTR | FIPS 197 | 256-bit | 128-bit post-quantum (Grover) |
|
- `WITH_PQBOX=1` independently enables the libpqvaptvupt sealed-box mode using
|
||||||
| Authentication | HMAC-SHA256 | RFC 2104 | 256-bit | 128-bit post-quantum (Grover) |
|
its separately installed system development package.
|
||||||
| Password KDF (default) | PBKDF2-SHA256 | RFC 8018 | 600K iterations | Password-dependent |
|
|
||||||
| Password KDF (WITH_SDK=1 option) | Argon2id | RFC 9106 | OWASP minimums | Password-dependent, memory-hard |
|
|
||||||
| Post-quantum KEM | ML-KEM-768 | FIPS 203 (validated vs OpenSSL 3.5) | 1184B ek / 2400B dk | NIST Level 3 |
|
|
||||||
| Classical KEM | X25519 | RFC 7748 | 32B scalar | ~128-bit classical |
|
|
||||||
| Hybrid KDF (`--pq`) | SHA3-512 | FIPS 202 | 512-bit output | Secure if either KEM holds |
|
|
||||||
| PQ-only KDF (`--pq-only`) | SHA3-512 | FIPS 202 | 512-bit output | Secure if ML-KEM-768 holds (no classical fallback) |
|
|
||||||
| Integrity | XXH64 | xxHash spec | 64-bit checksum | Non-cryptographic |
|
|
||||||
| Hashing | SHA3-256, SHA3-512 | FIPS 202 | 256/512-bit | Standard |
|
|
||||||
| Random | OS CSPRNG | getrandom(2) / RtlGenRandom | N/A | Hard fail if unavailable |
|
|
||||||
|
|
||||||
The default build uses PBKDF2-SHA256 (600k iterations) for password mode.
|
Neither library is committed as a precompiled artifact, and no build path
|
||||||
Argon2id is available only in a `make WITH_SDK=1` build.
|
downloads it. A missing requested dependency is a build error. Security
|
||||||
|
properties of these optional libraries are outside the source-only CLI audit
|
||||||
|
unless their exact source package and version are reviewed separately.
|
||||||
|
|
||||||
---
|
## Cryptographic construction
|
||||||
|
|
||||||
## Security Architecture
|
Encrypted blocks use a fresh 128-bit nonce, AES-256-CTR, and HMAC-SHA256. The
|
||||||
|
MAC binds the encrypted payload, canonical block metadata, and the frame's
|
||||||
|
logical position, and is checked before restored data is accepted. In 5.2.2,
|
||||||
|
this positional AAD applies to DATA and DEDUP_REF frames. An authenticated
|
||||||
|
reference is bound to its own position and carries the authenticated source
|
||||||
|
position needed to verify the referenced DATA frame.
|
||||||
|
|
||||||
### Per-Block Authenticated Encryption
|
An archive-integrity trailer (AIT) covers global metadata. The 5.2.2
|
||||||
|
`extract`, `list`, `test`, and `disk restore` paths refuse a no-AIT layout by
|
||||||
|
default, without trusting the archive's unauthenticated `ENCRYPTED` bit to
|
||||||
|
decide whether that check matters. `--allow-legacy-no-ait` is accepted only by
|
||||||
|
those commands; it is a recovery-only opt-in for a known, trusted pre-AIT
|
||||||
|
archive and emits a downgrade warning. Never use it for an archive from
|
||||||
|
untrusted or attacker-writable storage. `info` only reports unauthenticated
|
||||||
|
framing and apparent AIT presence; it does not validate the trailer or archive
|
||||||
|
contents. Plain archives use non-cryptographic checksums and do not provide
|
||||||
|
protection against an attacker who can rewrite the archive.
|
||||||
|
|
||||||
```
|
Archive comments are authenticated according to the archive mode, but they are
|
||||||
For each data block:
|
still untrusted display data. ZUPT renders control bytes safely when showing a
|
||||||
|
comment and does not emit raw terminal-control sequences. This prevents a valid
|
||||||
|
or attacker-supplied comment from rewriting terminal output; it does not make a
|
||||||
|
plain archive cryptographically authentic.
|
||||||
|
|
||||||
nonce = CSPRNG(16) [16 bytes, fresh per block]
|
In new 5.2.2 encrypted+dedup archives, each reference offset is included in
|
||||||
ciphertext = AES-256-CTR(enc_key, nonce, plaintext)
|
the authenticated reference payload. New encrypted disk archives also
|
||||||
mac = HMAC-SHA256(mac_key, aad ‖ nonce ‖ ciphertext) [32 bytes]
|
authenticate their index; the index binds the image size, block count, and a
|
||||||
stored = nonce ‖ ciphertext ‖ mac
|
chained XXH64 hash of the complete restored byte stream. The writer
|
||||||
|
additionally requires an XXH64 and SHA-256/128 match before emitting a dedup
|
||||||
|
reference, but that SHA-256/128 digest is an in-memory collision guard and is
|
||||||
|
not stored as an on-disk integrity claim. XXH64 is non-cryptographic: in a
|
||||||
|
plain archive it
|
||||||
|
detects accidental corruption but can be recomputed by an attacker.
|
||||||
|
|
||||||
|
The native hybrid derivation implemented by 5.2.2 is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ml_ss = ML-KEM-768 shared secret
|
||||||
|
x25519_ss = X25519 shared secret
|
||||||
|
hybrid_ikm = ml_ss XOR x25519_ss
|
||||||
|
archive_key = SHA3-512(hybrid_ikm || ml_ct || ephemeral_pk ||
|
||||||
|
"ZUPT-HYBRID-v1")
|
||||||
```
|
```
|
||||||
|
|
||||||
The nonce is a **fresh 128-bit random value per block**, stored in the block
|
`--pq-only` derives the archive key as
|
||||||
prefix and bound into the block MAC. The block sequence number is bound into
|
`SHA3-512(ml_ss || ml_ct || "ZUPT-PQ-ONLY-v1")`.
|
||||||
the MAC AAD (not into the nonce), so reordering, splicing, or replaying blocks
|
|
||||||
is still detected.
|
|
||||||
|
|
||||||
> **History (fixed in 4.2.0):** earlier releases derived the nonce as
|
The native modes are at-rest archive encryption. They do not provide protocol
|
||||||
> `base_nonce XOR pad_le(block_seq, 8)`. In `--dedup` mode every data block is
|
session forward secrecy: later compromise of the relevant long-term private key
|
||||||
> assigned sequence 0 (the sentinel that keeps cross-file dedup references
|
can compromise archives encrypted to it.
|
||||||
> authenticating consistently), so the nonce collapsed to a single value across
|
|
||||||
> all dedup blocks — reusing the AES-CTR keystream across distinct plaintexts
|
|
||||||
> (a many-time-pad). Switching to a fresh random per-block nonce closes this.
|
|
||||||
> Regression test: `tests/test_dedup_nonce.sh`. Re-encrypt any `--dedup` +
|
|
||||||
> encrypted archives written by ≤ 4.1.0.
|
|
||||||
|
|
||||||
### Encrypt-then-MAC
|
## Constant-time and side-channel scope
|
||||||
|
|
||||||
HMAC is computed over `nonce ‖ ciphertext` and verified **before** any
|
Portable C is the 5.2.2 default. Sensitive comparisons and selections use
|
||||||
decryption. This prevents:
|
branchless helpers, but generated machine-code behavior remains dependent on
|
||||||
|
the compiler and platform. This is not a formal whole-program constant-time
|
||||||
|
claim. The C AES implementation uses table lookups and is unsuitable for a
|
||||||
|
claim of cache-timing resistance on hostile shared hardware.
|
||||||
|
|
||||||
- Chosen-ciphertext attacks
|
Textual assembly under `jasmin/` can be enabled explicitly with
|
||||||
- Padding oracle attacks
|
`WITH_JASMIN=1` on a supported x86_64 compiler target. The directory contains
|
||||||
- Processing of tampered data
|
Jasmin-generated output and separately identified hand-written assembly; all of
|
||||||
|
it is disabled by default and its inclusion must be confirmed in the exact
|
||||||
|
binary being assessed. Its availability does not imply formal verification of
|
||||||
|
the archive parser, compression codec, or the whole program.
|
||||||
|
|
||||||
### Hybrid Post-Quantum KEM (`--pq`)
|
## Security boundary and limitations
|
||||||
|
|
||||||
> **FIPS 203 conformance (v5.0.0).** The ML-KEM-768 implementation is validated
|
ZUPT is designed for backups created and restored on trusted endpoints. It
|
||||||
> byte-for-byte against OpenSSL 3.5's FIPS 203 ML-KEM-768: deterministic keygen
|
does not protect against:
|
||||||
> produces an identical `ek`, and the shared secret agrees in both
|
|
||||||
> cross-decapsulation directions (our encaps ↔ OpenSSL decaps, and vice-versa).
|
|
||||||
> This is checked on every `make check` by `tests/test_mlkem_fips203.sh`.
|
|
||||||
> Releases ≤ 4.2.1 used round-3 CRYSTALS-Kyber (secure, but not interoperable);
|
|
||||||
> 5.0.0's `--pq`/`--pq-only` archives are therefore not backward-compatible.
|
|
||||||
|
|
||||||
```
|
- malware, keyloggers, memory inspection, or a compromised user account on the
|
||||||
Encapsulation:
|
machine handling plaintext or keys;
|
||||||
ML-KEM-768.Encaps(pk) → ml_ct[1088], ml_ss[32]
|
- disclosure of a password or private key;
|
||||||
eph_sk ← CSPRNG(32)
|
- denial of service from arbitrarily large or adversarial input;
|
||||||
eph_pk = X25519(eph_sk, basepoint)
|
- traffic analysis from archive size and visible framing metadata;
|
||||||
x25519_ss = X25519(eph_sk, recipient_pk)
|
- hiding that a file is a ZUPT archive;
|
||||||
hybrid_ikm = ml_ss XOR x25519_ss
|
- compression-length side channels when attacker-controlled and secret data are
|
||||||
archive_key = SHA3-512(hybrid_ikm ‖ ml_ct ‖ eph_pk ‖ "ZUPT-HYBRID-v1")
|
compressed together and an attacker can observe output length;
|
||||||
enc_key = archive_key[0:32]
|
- network transport attacks, multi-party access control, threshold recovery, or
|
||||||
mac_key = archive_key[32:64]
|
key rotation;
|
||||||
|
- every compiler-, microarchitecture-, power-, or speculative-execution side
|
||||||
|
channel.
|
||||||
|
|
||||||
|
Archive entry paths reject traversal, absolute paths, control characters,
|
||||||
|
Windows alternate streams/device names, and ambiguous trailing dot/space
|
||||||
|
components. POSIX extraction resolves each parent relative to a pinned file
|
||||||
|
descriptor with no-follow semantics after canonicalizing the user-selected
|
||||||
|
output root once; symlinks below that root remain forbidden. Windows resolves
|
||||||
|
each parent and temporary
|
||||||
|
file relative to a directory handle and performs the final no-replace rename by
|
||||||
|
handle, so a checked path is not looked up again through a mutable junction or
|
||||||
|
reparse point. An existing destination leaf is never overwritten.
|
||||||
|
|
||||||
|
Decoded bytes first go to a private, exclusively created temporary file. The
|
||||||
|
final name is published only after the expected decoded size and chained
|
||||||
|
checksum match and the stream closes successfully; failures remove the
|
||||||
|
temporary by descriptor/handle. These controls reduce traversal, link, race,
|
||||||
|
and partial-output risks, but they do not make privileged extraction
|
||||||
|
appropriate. Extract untrusted archives as a dedicated unprivileged user into
|
||||||
|
a new empty directory, inspect the result before moving it, and apply OS
|
||||||
|
sandboxing where available.
|
||||||
|
|
||||||
|
Disk restore has a separate destructive-device boundary. It measures and
|
||||||
|
copies the compacted archive to an exclusively created, auto-deleted private
|
||||||
|
scratch file before opening the target, then performs both validation and
|
||||||
|
restoration from that same snapshot. `ZUPT_TMPDIR` is an explicit existing
|
||||||
|
scratch-directory override; an invalid override fails without fallback. Raw
|
||||||
|
block devices are opened only after their capacity has been determined and
|
||||||
|
shown sufficient on supported Linux, macOS, or FreeBSD interfaces. Unknown
|
||||||
|
device capacity, an undersized device, a source/destination identity match, or
|
||||||
|
a snapshot failure stops before the first target write. These checks do not
|
||||||
|
make raw-device restore non-destructive: verify both operands and keep recovery
|
||||||
|
media before proceeding.
|
||||||
|
|
||||||
|
The Windows handle-relative implementation is scoped to normal local Win32
|
||||||
|
paths. Win32 extended-length and device-namespace paths, raw UNC output roots,
|
||||||
|
and mapped/network-drive output are not supported in 5.2.2. Cross-build and
|
||||||
|
Wine results are not native-Windows evidence; the `windows-latest` package gate
|
||||||
|
must pass its Unicode round trip before Windows assets are published. Restore
|
||||||
|
to a normal local directory first and move verified output to network storage
|
||||||
|
afterward.
|
||||||
|
|
||||||
|
## Historical compatibility and fixes
|
||||||
|
|
||||||
|
These statements are historical release records, not claims that every current
|
||||||
|
gate was rerun on every platform:
|
||||||
|
|
||||||
|
- In 4.2.0, encrypted deduplication changed from a repeated derived nonce to a
|
||||||
|
fresh random per-block nonce. Re-encrypt encrypted `--dedup` archives written
|
||||||
|
by releases through 4.1.0.
|
||||||
|
- In 5.0.0, native ML-KEM was corrected from round-3 CRYSTALS-Kyber semantics
|
||||||
|
to FIPS 203 ML-KEM-768. Native `--pq` and `--pq-only` keys and archives from
|
||||||
|
releases through 4.2.1 are not compatible with the corrected mode. Password
|
||||||
|
and plain archive paths were not affected by that KEM change.
|
||||||
|
- Releases predating the archive-integrity trailer may have a structurally
|
||||||
|
valid no-AIT layout. Such an archive now fails closed unless the caller uses
|
||||||
|
`--allow-legacy-no-ait` on a supported read command. This option permits
|
||||||
|
recovery of trusted old media; it is not a general compatibility mode and
|
||||||
|
does not make unauthenticated header/footer metadata safe.
|
||||||
|
- The 5.2.2 reader accepts the fixed-width disk index and encrypted-dedup linear
|
||||||
|
AAD sequence published through 5.2.1 and warns that the legacy index has no
|
||||||
|
whole-image content hash. Its regression fixture is an actual v5.2.1
|
||||||
|
password-encrypted DATA/DATA/REF/DATA disk archive stored as hexadecimal text with
|
||||||
|
source and hash provenance. The candidate lists, tests, extracts, and restores it
|
||||||
|
byte-exact; the exact final candidate must repeat that gate. This does not
|
||||||
|
claim that 5.2.1 readers accept the new flag-gated 5.2.2 records or that every
|
||||||
|
historical encrypted+dedup combination was validated.
|
||||||
|
|
||||||
|
The repository contains NIST/RFC vector tests and an OpenSSL 3.5 ML-KEM
|
||||||
|
interoperability test. The OpenSSL test can only execute when the environment
|
||||||
|
provides an ML-KEM-capable OpenSSL; otherwise it must be reported as skipped.
|
||||||
|
|
||||||
|
## Source and release integrity
|
||||||
|
|
||||||
|
Git and upstream source archives contain no compiled executable, object,
|
||||||
|
shared/static library, or distribution package. Audit them with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scripts/check-source-only.sh
|
||||||
|
scripts/check-source-only.sh --archive /path/to/zupt-5.2.2.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
Security model: secure if EITHER ML-KEM-768 (post-quantum, NIST Level 3)
|
Nested archive inspection is required to enforce bounded recursion, member
|
||||||
OR X25519 (classical, ~128-bit) remains unbroken. Both must be compromised
|
count, per-entry expanded size, and total expanded size, and to fail closed on
|
||||||
simultaneously to recover the archive key. Same approach as Signal
|
limit violations. The resource-limit regressions and all other late self-audit
|
||||||
(PQXDH), Apple iMessage (PQ3), and OpenSSH 9.0+.
|
fixes remain pending until the exact candidate completes the final gate suite.
|
||||||
|
|
||||||
The `--pq-sdk` mode (WITH_SDK=1 only) uses an HKDF-SHA3-256 combiner, a
|
DEB, binary RPM, SRPM, notice-bearing Linux tar.xz, source-only portable GUI
|
||||||
32-byte key commitment tag, HPKE-style context binding (RFC 9180 §5),
|
ZIP, Windows ZIP, and macOS DMG release assets are separate outputs. An
|
||||||
anti-fault double decapsulation, and XChaCha20-Poly1305 AEAD.
|
AppImage is not promoted for 5.2.2. A bare Linux or Windows executable is also
|
||||||
|
excluded; executables are distributed only inside their notice-bearing
|
||||||
|
archives. Trust an artifact only when its exact format has a recorded build,
|
||||||
|
content/metadata inspection, extracted or installed smoke test, and applicable
|
||||||
|
archive round trip. Never treat an unexecuted platform as passing.
|
||||||
|
|
||||||
### Full Post-Quantum KEM (`--pq-only`)
|
The gated 5.2.2 set is the CLI package/archive set plus the exact GUI DEB,
|
||||||
|
noarch/source RPM, and source-only portable ZIP documented in the README. The
|
||||||
|
portable GUI ZIP contains no compiled runtime and is scanned as source before
|
||||||
|
and after extraction. Other GUI packages, AppImage, AppDir and Flatpak bundles,
|
||||||
|
and GUI platform installers are excluded. Windows ZIP and macOS DMG artifacts
|
||||||
|
remain CLI-only.
|
||||||
|
|
||||||
```
|
## Reproducing project checks
|
||||||
Encapsulation:
|
|
||||||
ML-KEM-768.Encaps(pk) → ml_ct[1088], ml_ss[32]
|
Start with the baseline source-only build:
|
||||||
archive_key = SHA3-512(ml_ss ‖ ml_ct ‖ "ZUPT-PQ-ONLY-v1")
|
|
||||||
enc_key = archive_key[0:32]
|
```sh
|
||||||
mac_key = archive_key[32:64]
|
make clean
|
||||||
|
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 1)" \
|
||||||
|
WITH_SDK=0 WITH_PQBOX=0 V=1
|
||||||
|
make WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
|
make WITH_SDK=0 WITH_PQBOX=0 test-all
|
||||||
```
|
```
|
||||||
|
|
||||||
Security model: secure if ML-KEM-768 (post-quantum, NIST Level 3) remains
|
Where the compiler supports them, run the sanitizer target separately:
|
||||||
unbroken. **There is no classical component**, so — unlike `--pq` — a break of
|
|
||||||
ML-KEM-768 alone is sufficient to compromise the archive key. This mode exists
|
|
||||||
only for compliance postures that mandate a single NIST-standardised PQ
|
|
||||||
primitive with no classical KEM in the envelope (CNSA 2.0-style "PQ-only").
|
|
||||||
Decapsulation uses ML-KEM Fujisaki-Okamoto implicit rejection: a wrong or
|
|
||||||
tampered `ml_ct` yields a pseudorandom shared secret, so decryption fails
|
|
||||||
closed at the HMAC check rather than leaking a decapsulation-validity oracle.
|
|
||||||
**Unless a policy forbids the classical component, prefer `--pq`.**
|
|
||||||
|
|
||||||
---
|
```sh
|
||||||
|
|
||||||
## Constant-Time Guarantees
|
|
||||||
|
|
||||||
### Jasmin-Verified (assembly linked into binary)
|
|
||||||
|
|
||||||
| Function | Purpose | Proof |
|
|
||||||
|----------|---------|-------|
|
|
||||||
| `zupt_mac_verify_ct` | HMAC comparison (32 bytes) | Jasmin type system: no branch on diff value |
|
|
||||||
| `zupt_ct_select_32` | ML-KEM FO implicit rejection | Jasmin type system: no branch on cond value |
|
|
||||||
|
|
||||||
These functions are compiled from Jasmin source to x86-64 assembly. The
|
|
||||||
Jasmin compiler enforces that no secret-typed variable flows into branch
|
|
||||||
conditions or memory addresses. This guarantee holds at the machine code
|
|
||||||
level — no C compiler optimization can introduce timing leaks.
|
|
||||||
|
|
||||||
### C Constant-Time (branchless, compiler-dependent)
|
|
||||||
|
|
||||||
| Function | Method | Risk |
|
|
||||||
|----------|--------|------|
|
|
||||||
| X25519 `fe_cswap` | Masked XOR (`mask & (a ^ b)`) | Low — branchless but compiler may optimize |
|
|
||||||
| ML-KEM NTT/basemul | Montgomery reduction (no branches) | Low |
|
|
||||||
| ML-KEM CBD sampling | Bitwise operations only | Low |
|
|
||||||
| Key wipe (`zupt_secure_wipe`) | `explicit_bzero` / volatile | Low |
|
|
||||||
|
|
||||||
### NOT Constant-Time (documented risks)
|
|
||||||
|
|
||||||
| Function | Risk | Mitigation |
|
|
||||||
|----------|------|------------|
|
|
||||||
| AES-256 block encrypt | HIGH on shared hardware — S-box table lookups leak via cache timing | Jasmin AES-NI path planned; do not use on multi-tenant VMs |
|
|
||||||
| SHA-256 | Low — table constants are public, not indexed by secret data | Accepted |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Threat Model
|
|
||||||
|
|
||||||
### What VaptVupt Protects
|
|
||||||
|
|
||||||
| Asset | Protection |
|
|
||||||
|-------|-----------|
|
|
||||||
| File contents | AES-256-CTR encryption |
|
|
||||||
| File names, sizes, structure | Encrypted in central index block, HMAC-protected |
|
|
||||||
| Archive integrity (payloads + index) | Per-block HMAC-SHA256 |
|
|
||||||
| Archive integrity (header + footer metadata) | v1.5+ archives: 32-byte archive-integrity-trailer HMAC-SHA256 over `hdr ‖ ft[0..23]`. v1.4 archives: not covered, downgrade warning on extract. |
|
|
||||||
| Against stolen backups | AES-256 requires key/password to read |
|
|
||||||
| Against tampering of file contents, names, sizes, offsets | HMAC detects any modification |
|
|
||||||
| Against tampering of per-block frame preface bytes (codec_id, block_flags, varints, plaintext-XXH64) | v1.6: per-block MAC binds the canonical preface AAD; encryption-header block validated structurally |
|
|
||||||
| Against tampering of archive comment (when present) | Comment block goes through the same per-block AEAD pipeline as data (AES-256-CTR + HMAC-SHA256 + preface AAD); `hdr.comment_offset` pointer is in the AIT-signed region |
|
|
||||||
| Against block-swap (reorder) attacks | MAC binds an 8-byte position AAD; a block moved to another position fails verification and its partial output is unlinked. Dedup refs use sentinel seq=0 and rely on plaintext XXH64 for per-block integrity. |
|
|
||||||
| Against malicious archive entries (Zip Slip / path traversal) | `zupt_path_is_safe()` rejects `..`, absolute paths, Windows drive/UNC paths, embedded NULs |
|
|
||||||
| Against symlink at extract target (TOCTOU) | `zupt_safe_fopen_output()` uses `O_NOFOLLOW` on POSIX. Windows relies on directory ACLs (documented limitation). |
|
|
||||||
| Against quantum adversary | `--pq` mode: ML-KEM-768 (NIST Level 3) hybridized with X25519 |
|
|
||||||
|
|
||||||
The wire/on-disk format is v1.6. See CHANGELOG.md for the per-release
|
|
||||||
finding history behind these protections.
|
|
||||||
|
|
||||||
### What VaptVupt Does NOT Protect Against
|
|
||||||
|
|
||||||
| Threat | Reason | Mitigation Path |
|
|
||||||
|--------|--------|----------------|
|
|
||||||
| Attacker who knows the password or has the private key | Fundamental to encryption | Use strong passwords (12+ chars); protect key files |
|
|
||||||
| Endpoint compromise (keylogger, malware on the host) | Outside the archive's trust boundary | Secure the machine where you type the password or hold the key |
|
|
||||||
| Cache-timing side channels (C AES) | Table-based S-box lookups | Build with Jasmin AES-NI when available; avoid multi-tenant VMs |
|
|
||||||
| Memory forensics during operation | Keys on stack during compress/extract | `zupt_secure_wipe()` on completion; `mlock()` planned |
|
|
||||||
| Deniability | Archive header identifies format | `.zupt` magic bytes visible; ENCRYPTED flag in header |
|
|
||||||
| Weak passwords | PBKDF2-SHA256 (600k) is the default KDF; Argon2id (memory-hard) is available in a WITH_SDK=1 build | Use `--pq` mode for critical data — keys are random, not derived from a password |
|
|
||||||
| Traffic analysis / metadata | Archive size reveals data volume; file list, sizes, mtimes not padded | Outside VaptVupt's scope |
|
|
||||||
| File permission/ownership | Not stored in archive | Documented in README.md |
|
|
||||||
| Spectre-class side channels in callers | Below the constant-time primitive layer | Host OS / compiler mitigations |
|
|
||||||
|
|
||||||
### Quantum Threat Analysis
|
|
||||||
|
|
||||||
Scenario: adversary captures an encrypted archive today, stores it, and
|
|
||||||
attempts decryption when a cryptographically-relevant quantum computer is
|
|
||||||
available.
|
|
||||||
|
|
||||||
| Mode | Classical Security | Quantum Security | Verdict |
|
|
||||||
|------|-------------------|-----------------|---------|
|
|
||||||
| Password (`-p`) | Password-dependent + 256-bit AES | ~128-bit (Grover on AES), PBKDF2 accelerated | Vulnerable — use `--pq` |
|
|
||||||
| PQ Hybrid (`--pq`) | ~128-bit (X25519) | NIST Level 3 (ML-KEM-768) | Protected |
|
|
||||||
|
|
||||||
In `--pq` mode: even if Shor's algorithm breaks X25519, ML-KEM-768
|
|
||||||
protects the archive; even if a novel classical attack breaks ML-KEM,
|
|
||||||
X25519 still provides ~128-bit security. The hybrid design is secure if
|
|
||||||
either component holds.
|
|
||||||
|
|
||||||
### Extracting untrusted archives — operational guidance
|
|
||||||
|
|
||||||
The in-binary defenses are the primary control; the following are defense
|
|
||||||
in depth:
|
|
||||||
|
|
||||||
1. Extract into a dedicated empty directory (not `~/Downloads` or `/tmp`).
|
|
||||||
2. Audit symlinks in the target directory before extraction.
|
|
||||||
3. Run extraction as a low-privilege user, never root.
|
|
||||||
4. On Windows, pre-create the target directory with restrictive ACLs
|
|
||||||
(the `O_NOFOLLOW` defense is POSIX-only).
|
|
||||||
|
|
||||||
### Out of scope
|
|
||||||
|
|
||||||
- External independent audit.
|
|
||||||
- Side-channel testing on production hardware (timing leaks).
|
|
||||||
- Formal verification beyond the Jasmin constant-time primitives.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CSPRNG Policy
|
|
||||||
|
|
||||||
| Platform | Primary Source | Fallback | Failure Mode |
|
|
||||||
|----------|---------------|----------|--------------|
|
|
||||||
| Linux | `getrandom(2)` | `/dev/urandom` | Hard exit — no encryption without CSPRNG |
|
|
||||||
| macOS | `/dev/urandom` | None | Hard exit |
|
|
||||||
| Windows | `RtlGenRandom` | None | Hard exit |
|
|
||||||
|
|
||||||
There is no `rand()`, `srand()`, or any weak PRNG fallback anywhere in the
|
|
||||||
codebase. If the OS CSPRNG is unavailable, VaptVupt exits with an error.
|
|
||||||
This is a deliberate design choice — weak random keys are worse than no
|
|
||||||
encryption.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Supported Platforms
|
|
||||||
|
|
||||||
| Platform | Compiler | Threading | CSPRNG | Status |
|
|
||||||
|----------|----------|-----------|--------|--------|
|
|
||||||
| Linux x86-64 | GCC 5+ / Clang 3.5+ | pthreads | `getrandom(2)` | Primary |
|
|
||||||
| Linux ARM64 | GCC 5+ | pthreads | `getrandom(2)` | Tested |
|
|
||||||
| macOS x86-64/ARM64 | Apple Clang | pthreads | `/dev/urandom` | Tested |
|
|
||||||
| Windows x86-64 | MinGW / MSVC 2015+ | Win32 threads | `RtlGenRandom` | Tested |
|
|
||||||
| FreeBSD | GCC / Clang | pthreads | `/dev/urandom` | Untested (expected to work) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Verification Commands
|
|
||||||
|
|
||||||
Anyone can verify the security claims. The default build needs only a C
|
|
||||||
compiler + make (plus libm/pthread); no external crypto library.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build
|
|
||||||
make
|
|
||||||
|
|
||||||
# Functional tests
|
|
||||||
make test-all
|
|
||||||
|
|
||||||
# Memory safety
|
|
||||||
make test-asan
|
make test-asan
|
||||||
|
make test-asan-run
|
||||||
# NIST/RFC test vectors
|
|
||||||
make test-vectors && ./test_vectors
|
|
||||||
|
|
||||||
# Verify Jasmin symbols are active
|
|
||||||
nm vaptvupt | grep "zupt_mac_verify_ct\|zupt_ct_select_32"
|
|
||||||
# Expected: T zupt_mac_verify_ct
|
|
||||||
# T zupt_ct_select_32
|
|
||||||
|
|
||||||
# Verify Jasmin compilation (requires jasminc)
|
|
||||||
jasminc -arch x86-64 -o /dev/null jasmin/zupt_mac_verify.jazz
|
|
||||||
jasminc -arch x86-64 -o /dev/null jasmin/zupt_mlkem_select.jazz
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
The first command builds the sanitizer configuration; the second executes its
|
||||||
|
test suite. Neither substitutes for the normal optimized build and tests.
|
||||||
|
|
||||||
© 2026 Cristian Cezar Moisés — AGPL-3.0-or-later (dual-licensed AGPL + commercial)
|
Because the positional-AAD and mandatory-AIT behavior changed late in the
|
||||||
|
candidate, earlier successful runs are intermediate evidence only. The exact
|
||||||
|
release candidate must rerun the affected regressions and the complete required
|
||||||
|
suite; unavailable environments remain `SKIP`, not `PASS`.
|
||||||
|
|
||||||
|
The same rule applies to private-key creation/parsing, terminal-safe comment
|
||||||
|
display, POSIX prompt signal cleanup, explicit Bash regression execution, and
|
||||||
|
source-scanner resource limits added during the final self-audit. This document
|
||||||
|
records intended candidate behavior, not a final `PASS` for work still being
|
||||||
|
integrated or rerun.
|
||||||
|
|
||||||
|
Run target-native static analyzers and package checks as additional evidence.
|
||||||
|
Do not infer x86_64, aarch64, ppc64le, s390x, riscv64, macOS, Windows, Leap, or
|
||||||
|
SLE success from these commands unless that exact environment produced a
|
||||||
|
successful recorded result.
|
||||||
|
|
||||||
|
ZUPT application code is distributed under AGPL-3.0-or-later. The bundled
|
||||||
|
VaptVupt codec source is GPL-3.0-or-later. The two xxHash-derived XXH64 units
|
||||||
|
also carry BSD-2-Clause. The pq-crystals/kyber-derived portions of native
|
||||||
|
ML-KEM carry CC0-1.0 in addition to the application license, and the x86 BCJ
|
||||||
|
state machine is adapted from public-domain LZMA SDK source. Native X25519
|
||||||
|
portions adapted from curve25519-donna conservatively retain BSD-3-Clause. See
|
||||||
|
`LICENSE`, `LICENSE-GPL-3.0`, `LICENSE-BSD-2-Clause`, `LICENSE-BSD-3-Clause`,
|
||||||
|
`LICENSE-CC0-1.0`, `NOTICE`, and `THIRD-PARTY-NOTICES.md`. Historical license
|
||||||
|
grants for exact earlier material are recorded in the 5.2.2 licensing erratum;
|
||||||
|
the current notices do not revoke them.
|
||||||
|
|
|
||||||
|
|
@ -1,110 +1,179 @@
|
||||||
THIRD-PARTY NOTICES
|
# Third-party and bundled-component notices
|
||||||
===================
|
|
||||||
|
|
||||||
This document records VaptVupt's runtime dependencies and build-time
|
This file records bundled source, generated textual source and optional system
|
||||||
tools. If you redistribute VaptVupt, you must preserve this attribution
|
dependencies. Preserve it with LICENSE, NOTICE, and the applicable license
|
||||||
document along with the LICENSE file.
|
texts.
|
||||||
|
|
||||||
-------------------------------------------------------------------------
|
## Bundled VaptVupt codec
|
||||||
Licensing
|
|
||||||
-------------------------------------------------------------------------
|
|
||||||
|
|
||||||
**Note on VaptVupt LZ codec licensing**: the VaptVupt LZ codec
|
The compression codec in src/vv_*.c, src/vaptvupt_api.c,
|
||||||
(src/vv_*.c, src/vaptvupt_api.c, include/vaptvupt*.h) is licensed
|
include/vaptvupt*.h, and include/vv_*.h is bundled as source and licensed
|
||||||
GPL-3.0-or-later (not AGPL like the rest of the project) so that, with
|
GPL-3.0-or-later.
|
||||||
sufficient maturity, it can be considered for upstreaming into the Linux
|
|
||||||
or BSD kernels, which require GPL-compatible licenses. The author retains
|
|
||||||
the right to dual-license the codec under other terms for commercial use;
|
|
||||||
contact sac@securityops.co for inquiries.
|
|
||||||
|
|
||||||
The rest of the project (vaptvupt CLI, Jasmin source, GUI) is licensed
|
- Recorded codec release: 2.65.3
|
||||||
AGPL-3.0-or-later. Commercial licenses (relief from the AGPL network-use
|
- Recorded upstream tag: v2.65.3
|
||||||
clause) are available; contact sac@securityops.co.
|
- Standalone upstream: https://git.securityops.co/cristiancmoises/vaptvupt-codec
|
||||||
|
- Integration commit in this repository:
|
||||||
|
59f9ebc59ea13c6edf1d199ca795cdbf00e62226
|
||||||
|
|
||||||
-------------------------------------------------------------------------
|
The integration commit records an in-tree ANS safe-zone reserve applied on top
|
||||||
Build-time tool (not redistributed)
|
of that tag. Earlier integration commit a2350dd also records wrapper-default
|
||||||
-------------------------------------------------------------------------
|
changes used by the CLI. This repository did not retain the standalone tag
|
||||||
|
object hash, so the tag name and the immutable integration commits are the
|
||||||
|
provenance evidence available here; no unverified external hash is asserted.
|
||||||
|
|
||||||
**jasminc** — the Jasmin language compiler
|
The openSUSE package truthfully declares
|
||||||
|
bundled(vaptvupt-codec) = 2.65.3. No compiled codec object or library is
|
||||||
|
distributed in the source tree or source archive.
|
||||||
|
|
||||||
The constant-time cryptographic primitives in jasmin/*.jazz are
|
## Jasmin and textual assembly
|
||||||
compiled to native assembly (jasmin/*.s) using the external `jasminc`
|
|
||||||
compiler. The jasminc tool is not bundled with VaptVupt; the AGPL .jazz
|
|
||||||
source files and their AGPL-licensed .s assembly output are bundled.
|
|
||||||
|
|
||||||
Upstream: https://github.com/jasmin-lang/jasmin
|
Files under `jasmin/` include AGPL-licensed `.jazz` source or algorithm
|
||||||
License: MIT (the compiler itself; not relevant to VaptVupt's licensing)
|
descriptions and textual GNU assembly `.s`. The assembly is source, not an
|
||||||
Used by: VaptVupt's build system, only when re-generating jasmin/*.s
|
object file. Provenance is recorded per production unit rather than treating
|
||||||
from jasmin/*.jazz (most users won't need to do this —
|
every `.s` file as generated:
|
||||||
pre-built .s files ship in this repo).
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------
|
- `zupt_mac_verify.s`, `zupt_mlkem_select.s`, and `zupt_x25519_fe.s` identify
|
||||||
Runtime system libraries (linked from the OS, never bundled)
|
themselves as output of Jasmin Compiler 2026.03.0;
|
||||||
-------------------------------------------------------------------------
|
- `zupt_aes_ctr.s` is recorded in its file header as `jasminc` output, but the
|
||||||
|
exact compiler version was not retained in that file, so no version stronger
|
||||||
|
than the repository record is asserted;
|
||||||
|
- `zupt_aes_ctr4.s` is hand-written production assembly matching the algorithm
|
||||||
|
documented by `zupt_aes_ctr4.jazz`; that `.jazz` file is not compiled.
|
||||||
|
|
||||||
These are standard system libraries provided by the operating system's
|
Regeneration of files identified as compiler output uses the external
|
||||||
package manager (apt, dnf, pacman, etc.). They are dynamically linked
|
`jasminc` compiler:
|
||||||
at runtime and are NOT redistributed as part of VaptVupt.
|
|
||||||
|
|
||||||
**libargon2** — Argon2id password hashing function (RFC 9106)
|
- Upstream: https://github.com/jasmin-lang/jasmin
|
||||||
|
- Compiler license: MIT
|
||||||
|
|
||||||
Required only for: the optional `make WITH_SDK=1` build. The default
|
The compiler itself is not bundled or redistributed. Hand-written assembly
|
||||||
build uses native PBKDF2-SHA256 and does not link
|
must not be represented as generated or formally verified merely because a
|
||||||
libargon2.
|
corresponding `.jazz` description exists.
|
||||||
Linked at runtime: libargon2.so.1
|
|
||||||
Version expected: 1.0+ (Debian/Ubuntu: libargon2-1)
|
|
||||||
Upstream: https://github.com/P-H-C/phc-winner-argon2
|
|
||||||
License: Apache-2.0 OR CC0-1.0 (dual)
|
|
||||||
Copyright: (c) 2015 The Argon2 Authors
|
|
||||||
Used by: Argon2id password-derived encryption mode
|
|
||||||
|
|
||||||
**OpenSSL libcrypto** — AES, SHA-256, AES-NI hardware backends
|
## Optional system libraries
|
||||||
|
|
||||||
Linked at runtime: libcrypto.so.3
|
The default WITH_SDK=0 WITH_PQBOX=0 build uses the operating system's C runtime,
|
||||||
Version expected: 3.0+
|
math and threading libraries and does not bundle a shared library.
|
||||||
Upstream: https://www.openssl.org
|
|
||||||
License: Apache-2.0
|
|
||||||
Copyright: (c) 1998-2026 The OpenSSL Project
|
|
||||||
Used by: AES-256-CTR, SHA-256, hardware-accelerated paths
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------
|
WITH_SDK=1 and WITH_PQBOX=1 are opt-in integrations. They use only headers and
|
||||||
Compatibility with public standards
|
libraries supplied by the system/toolchain configuration and fail explicitly
|
||||||
-------------------------------------------------------------------------
|
when those dependencies are unavailable:
|
||||||
|
|
||||||
Where VaptVupt implements public standards, it does so independently from
|
- libvuptsdk: enables --pq-sdk and the Argon2id-backed SDK path;
|
||||||
any reference implementation. Other projects in the post-quantum hybrid
|
- libpqvaptvupt: enables --pq-box.
|
||||||
encryption space (libsodium, age, Tink, rustls, etc.) were referenced as
|
|
||||||
prior art during design, but no code was copied from any external
|
|
||||||
project. Standards followed:
|
|
||||||
|
|
||||||
- FIPS 197 (AES)
|
The former vendor/vuptsdk and vendor/pqvaptvupt header snapshots and all
|
||||||
- FIPS 202 (Keccak / SHA-3)
|
fallbacks to local precompiled libraries were removed. No download occurs in
|
||||||
- FIPS 203 (ML-KEM)
|
make, packaging build, or package checks.
|
||||||
- RFC 5297 (AES-SIV)
|
|
||||||
- RFC 5869 (HKDF)
|
|
||||||
- RFC 7748 (X25519)
|
|
||||||
- RFC 8032 (Ed25519)
|
|
||||||
- RFC 8439 (ChaCha20-Poly1305)
|
|
||||||
- RFC 9106 (Argon2)
|
|
||||||
- RFC 9180 (HPKE)
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------
|
## xxHash-derived source
|
||||||
Reporting attribution issues
|
|
||||||
-------------------------------------------------------------------------
|
|
||||||
|
|
||||||
If you believe VaptVupt redistributes code from a project not listed here,
|
`src/zupt_xxh.c` and `src/vv_xxh64.c` contain adapted XXH64 routines based on
|
||||||
or if attribution information is incomplete, please email:
|
xxHash by Yann Collet. xxHash is BSD-2-Clause, not public domain. The upstream
|
||||||
|
copyright, conditions, and disclaimer are preserved in
|
||||||
|
`LICENSE-BSD-2-Clause`; those obligations apply in addition to the AGPL or GPL
|
||||||
|
scope identified by each source file.
|
||||||
|
|
||||||
sac@securityops.co
|
- Upstream: https://github.com/Cyan4973/xxHash
|
||||||
|
- Upstream license: https://github.com/Cyan4973/xxHash/blob/dev/LICENSE
|
||||||
|
|
||||||
with the subject "[third-party]" and details of the issue.
|
## pq-crystals/kyber-derived ML-KEM source
|
||||||
|
|
||||||
-------------------------------------------------------------------------
|
`src/zupt_mlkem.c` contains portions adapted from the pq-crystals/kyber
|
||||||
License summary
|
reference implementation, including its NTT, base multiplication, Montgomery
|
||||||
-------------------------------------------------------------------------
|
conversion, and related representation conventions. The upstream project
|
||||||
|
offers that reference code under either CC0-1.0 or Apache-2.0; ZUPT elects
|
||||||
|
the CC0-1.0 option for those portions. Local integration and modifications
|
||||||
|
remain under AGPL-3.0-or-later, as recorded by the compound per-file SPDX
|
||||||
|
identifier.
|
||||||
|
|
||||||
VaptVupt CLI, Jasmin source, GUI: AGPL-3.0-or-later
|
- Upstream: https://github.com/pq-crystals/kyber
|
||||||
VaptVupt LZ codec: GPL-3.0-or-later
|
- Upstream license record: https://github.com/pq-crystals/kyber/blob/main/LICENSE
|
||||||
Commercial license (any component): contact sac@securityops.co
|
- Local introduction commit: c80332778fb10364a606bf0380f440dc7be66ced
|
||||||
|
- Local FIPS 203 correction commit: 862f4a2df6c756ebd0369e176ea68b5ac506f422
|
||||||
|
|
||||||
Project home: https://git.securityops.co/cristiancmoises/vaptvupt
|
The repository did not retain an immutable upstream Kyber revision for the
|
||||||
|
original adaptation. No unverified upstream commit is asserted. The complete
|
||||||
|
CC0-1.0 legal text is in `LICENSE-CC0-1.0`.
|
||||||
|
|
||||||
|
## curve25519-donna-derived X25519 source
|
||||||
|
|
||||||
|
`src/zupt_x25519.c` contains portions adapted from the 5x51-bit
|
||||||
|
curve25519-donna implementation, including its field representation, packing,
|
||||||
|
constant-time swap, and inversion-chain approach. The upstream source file
|
||||||
|
describes the code as public domain, while the repository preserves a
|
||||||
|
BSD-3-Clause notice. This distribution conservatively retains that complete
|
||||||
|
BSD-3-Clause notice in `LICENSE-BSD-3-Clause`; local integration and
|
||||||
|
modifications remain AGPL-3.0-or-later under the compound per-file SPDX
|
||||||
|
identifier.
|
||||||
|
|
||||||
|
- Upstream: https://github.com/agl/curve25519-donna
|
||||||
|
- Upstream license record: https://github.com/agl/curve25519-donna/blob/master/LICENSE.md
|
||||||
|
- Upstream copyright: Copyright 2008, Google Inc.
|
||||||
|
- Upstream author record: Adam Langley
|
||||||
|
- Local introduction commit: c80332778fb10364a606bf0380f440dc7be66ced
|
||||||
|
|
||||||
|
The repository did not retain an immutable upstream revision for the original
|
||||||
|
adaptation. No unverified upstream commit is asserted, and the historical
|
||||||
|
reference to libsodium is treated as an implementation comparison rather than
|
||||||
|
an unsupported claim that libsodium was the copied source.
|
||||||
|
|
||||||
|
## LZMA SDK x86 BCJ source
|
||||||
|
|
||||||
|
The x86 state machine in `src/vv_bcj.c` is adapted from Igor Pavlov's
|
||||||
|
`C/Bra86.c` in the LZMA SDK. The official LZMA SDK is placed in the public
|
||||||
|
domain. The AArch64 filter in the same file is separately documented local
|
||||||
|
code and is not represented as LZMA SDK source.
|
||||||
|
|
||||||
|
- Upstream: https://www.7-zip.org/sdk.html
|
||||||
|
- Upstream author: Igor Pavlov
|
||||||
|
- Upstream status: public domain
|
||||||
|
|
||||||
|
The exact SDK version or revision used by the original integration was not
|
||||||
|
retained, so none is asserted. The former `clean-room` description was removed
|
||||||
|
because repository evidence cannot establish that development process.
|
||||||
|
|
||||||
|
## SHA-Intrinsics SHA-NI source
|
||||||
|
|
||||||
|
The SHA-NI compression path in `src/zupt_sha256_shani.c` is adapted from
|
||||||
|
Jeffrey Walton's public-domain `SHA-Intrinsics/sha256-x86.c` reference, which
|
||||||
|
records that it is based on Intel and Sean Gulley's miTLS material. The
|
||||||
|
immutable upstream reference below explicitly places the code in the public
|
||||||
|
domain; it therefore adds no separate package-license term. Local integration
|
||||||
|
and modifications remain AGPL-3.0-or-later.
|
||||||
|
|
||||||
|
- Upstream: https://github.com/noloader/SHA-Intrinsics
|
||||||
|
- Audited source revision: d03795497f3e4576083fc2cd8fe0b924f24d0bb2
|
||||||
|
- Upstream source: https://github.com/noloader/SHA-Intrinsics/blob/d03795497f3e4576083fc2cd8fe0b924f24d0bb2/sha256-x86.c
|
||||||
|
- Upstream author: Jeffrey Walton
|
||||||
|
- Upstream status: public domain
|
||||||
|
- Local introduction commit: 544a2cd64758478690e33a923b2ab75347122f51
|
||||||
|
|
||||||
|
## GUI image data
|
||||||
|
|
||||||
|
The PNG and ICO files under gui/assets/ are non-executable first-party GUI data.
|
||||||
|
Their purpose, Git provenance and license scope, including the historical MIT
|
||||||
|
grant attached to their unchanged Git blobs, are recorded in
|
||||||
|
`gui/assets/README.md`.
|
||||||
|
|
||||||
|
## AppImage type-2 runtime
|
||||||
|
|
||||||
|
No AppImage is a promised or promoted 5.2.2 release asset. The upstream
|
||||||
|
type-2 runtime inspected for this release statically linked musl, libfuse,
|
||||||
|
squashfuse, zstd, zlib, and mimalloc, but its own license notice did not list
|
||||||
|
mimalloc and the available release inputs did not provide a complete
|
||||||
|
LGPL-compatible source/relink handoff. ZUPT therefore does not
|
||||||
|
redistribute that runtime.
|
||||||
|
|
||||||
|
`packaging/build-appimage.sh` remains an offline downstream helper. It accepts
|
||||||
|
no network input and requires the operator to supply both a locally verified
|
||||||
|
runtime and `APPIMAGE_RUNTIME_COMPLIANCE_FILE`, containing the license notices,
|
||||||
|
source correspondence or offer, and relink information applicable to those
|
||||||
|
exact runtime bytes. An artifact produced independently with that helper is
|
||||||
|
not covered by the 5.2.2 upstream release gates.
|
||||||
|
|
||||||
|
## Reporting attribution issues
|
||||||
|
|
||||||
|
Report incomplete or incorrect attribution to sac@securityops.co with the
|
||||||
|
subject [third-party].
|
||||||
|
|
|
||||||
618
THREAT_MODEL.md
618
THREAT_MODEL.md
|
|
@ -1,328 +1,298 @@
|
||||||
# VaptVupt threat model
|
# ZUPT 5.2.2 threat model
|
||||||
|
|
||||||
Plain-English description of what VaptVupt protects against, what it
|
This document defines the security boundary of the ZUPT archive tool. It is
|
||||||
doesn't, and what assumptions you're making when you use it.
|
not a certification, a guarantee against every hostile input, or a substitute
|
||||||
|
for reviewing the exact source and binary used for important data.
|
||||||
This document is for users and downstream packagers. Read it before
|
|
||||||
trusting VaptVupt with anything you can't afford to lose.
|
## Intended use
|
||||||
|
|
||||||
---
|
ZUPT is intended for at-rest backup archives created and restored on
|
||||||
|
machines controlled by the user. It can be used when the storage provider or
|
||||||
## TL;DR
|
physical medium is not trusted, provided encryption is enabled and credentials
|
||||||
|
remain secret.
|
||||||
VaptVupt is designed for at-rest backup encryption by someone who
|
|
||||||
controls the machine doing the encryption and the machine doing the
|
It is not a network protocol, a full-disk encryption system, a multi-party or
|
||||||
extraction. It is not a network protocol, a multi-party scheme, or
|
threshold scheme, a password manager, or a way to make an archive's existence
|
||||||
a substitute for full-disk encryption.
|
plausibly deniable.
|
||||||
|
|
||||||
| Use case | VaptVupt is appropriate? |
|
## Baseline considered here
|
||||||
|---|---|
|
|
||||||
| Backing up files to an untrusted cloud (S3, Backblaze, Google Drive) | Yes |
|
The upstream baseline is built from the 5.2.2 source with:
|
||||||
| Backing up a disk image to external media you might lose | Yes |
|
|
||||||
| Long-term archival of personal/business data | Yes |
|
```sh
|
||||||
| Sharing an encrypted archive with someone you trust to handle the key | Yes, with care (see "Key distribution" below) |
|
make WITH_SDK=0 WITH_PQBOX=0
|
||||||
| Real-time encrypted communication | No (use Signal, age, or TLS) |
|
```
|
||||||
| Multi-party access (n-of-m) | No (no threshold scheme) |
|
|
||||||
| Hiding the existence of an archive (steganography) | No (archive header has fixed magic bytes) |
|
It contains the native password, ML-KEM-768 + X25519 hybrid `--pq`, and
|
||||||
| Protecting against a hostile machine you're encrypting on | No (a compromised host can read plaintext before encryption) |
|
ML-KEM-768-only `--pq-only` modes. It does not load a precompiled library from
|
||||||
|
the repository and does not download a dependency while building.
|
||||||
---
|
|
||||||
|
`WITH_SDK=1` and `WITH_PQBOX=1` add separately installed system libraries and
|
||||||
## Modes referenced in this document
|
change the assessed code boundary. The SDK and PQBOX integrations must be
|
||||||
|
reviewed with their exact packaged source and version; success of the baseline
|
||||||
- `-p` / password mode: symmetric encryption with a key derived from a
|
tests is not evidence for them.
|
||||||
password. The default build derives the key with PBKDF2-SHA256
|
|
||||||
(600k iterations). Argon2id is available only in an upstream
|
Textual assembly under `jasmin/` is a separate `WITH_JASMIN=1` option for
|
||||||
`make WITH_SDK=1` build against the separately distributed
|
supported x86_64 compiler targets. The directory contains both generated and
|
||||||
libraries.
|
separately identified hand-written assembly. Portable C is the default.
|
||||||
- `--pq`: native post-quantum **hybrid** mode (ML-KEM-768 + X25519), the
|
Architecture portability is a source property, not evidence that an unexecuted
|
||||||
recommended PQ mode in the default build. The ML-KEM-768 implementation
|
architecture passed.
|
||||||
is in-tree.
|
|
||||||
- `--pq-only`: native **full/pure** post-quantum mode (ML-KEM-768 only, no
|
## Assets
|
||||||
X25519), also in the default build. For compliance postures that mandate a
|
|
||||||
single NIST-standardised PQ primitive with no classical KEM in the envelope.
|
The assets ZUPT tries to protect are:
|
||||||
Its threat profile differs from `--pq` in exactly one axis: it has no
|
|
||||||
classical fallback, so a break of ML-KEM-768 alone breaks the archive
|
- archived file contents and encrypted index data;
|
||||||
(see §5 and "Cryptographic assumptions").
|
- the integrity and ordering of encrypted archive blocks and current global
|
||||||
- `--pq-sdk` / `--pq-box`: optional post-quantum modes backed by the
|
metadata covered by the archive integrity trailer;
|
||||||
separately distributed `libzuptsdk` / `libpqvaptvupt` libraries.
|
- private keys, passwords, and derived encryption/MAC keys while held by the
|
||||||
Available only in a `make WITH_SDK=1` build. Key files for these
|
trusted caller;
|
||||||
modes are produced by `vaptvupt keygen --sdk`, also SDK-only.
|
- safe placement of extracted entries within the requested destination.
|
||||||
|
|
||||||
---
|
The archive's existence, total byte length, magic, encryption/framing flags, and
|
||||||
|
some size/structure information are observable. Plain archives provide
|
||||||
## What VaptVupt protects against
|
corruption detection, not cryptographic protection against an active attacker.
|
||||||
|
|
||||||
### 1. Confidentiality of archive contents (encrypted mode)
|
## Adversaries considered
|
||||||
|
|
||||||
An attacker with read access to the archive bytes cannot recover
|
The design considers an adversary who can read, copy, truncate, reorder, or
|
||||||
plaintext file contents, file names, file sizes, file modes, or
|
modify stored archive bytes but cannot read the encryption endpoint's memory or
|
||||||
embedded comments without the key/password, assuming:
|
credentials. It also considers accidental corruption and malicious archive
|
||||||
|
entry paths during extraction.
|
||||||
- The chosen mode is one of the encrypted modes (`-p`, `--pq`, or the
|
|
||||||
optional `--pq-sdk` / `--pq-box`)
|
The following adversaries are outside the protection boundary:
|
||||||
- The password is strong enough to resist offline brute-force
|
|
||||||
(see "Password strength" below)
|
- malware, a keylogger, or an administrator on the source or restore endpoint;
|
||||||
- The key file (for `--pq-sdk` / `--pq-box`) was not compromised at
|
- an attacker who obtains the password or matching private key;
|
||||||
generation time
|
- a malicious compiler, kernel, CPU, firmware, or random-number generator;
|
||||||
|
- an attacker with unrestricted side-channel observation of a shared machine;
|
||||||
### 2. Integrity of every byte of an encrypted archive
|
- an attacker allowed unbounded CPU, memory, or storage denial of service.
|
||||||
|
|
||||||
If any single bit of the on-disk archive bytes is flipped, the
|
## Security properties
|
||||||
extraction fails with an authentication error. Coverage layers:
|
|
||||||
|
### Encrypted archive confidentiality
|
||||||
- Per-block HMAC-SHA256 with frame-preface AAD (F-09): every data
|
|
||||||
block carries an HMAC over its ciphertext and over the canonical
|
Password and native PQ modes encrypt blocks with AES-256-CTR and authenticate
|
||||||
29-byte preface (block_type, codec_id, block_flags, sizes,
|
them with HMAC-SHA256. Confidentiality depends on unique nonces, correct
|
||||||
plaintext-XXH64)
|
implementations, OS randomness, and credential secrecy. In password mode it
|
||||||
- Archive Integrity Trailer (F-08): HMAC-SHA256 over the 64-byte
|
also depends on password entropy; PBKDF2-SHA256 slows but cannot prevent offline
|
||||||
header and 24 bytes of footer, appended after the footer
|
guessing of a weak password.
|
||||||
- Strict structural validation of the encryption-header block (F-09):
|
|
||||||
codec must be `STORE`, flags must be 0, csz must equal usz, the
|
Prefer `--password-prompt`, `--pass-file`, or `--pass-fd`. A password supplied
|
||||||
plaintext XXH64 must match
|
through `-p/--password` can be visible through process inspection or shell
|
||||||
|
history. A password file is protected only by the caller's filesystem choices;
|
||||||
### 3. Tamper detection on plaintext archives (best-effort)
|
ZUPT does not validate its ownership or permission bits. A descriptor is
|
||||||
|
trusted input inherited from the caller. Both non-interactive forms read one
|
||||||
Plaintext archives (no `-p`, no `--pq*`) are protected by XXH64
|
line and reject empty, NUL-containing, or overlong values. The descriptor form
|
||||||
plaintext checksums per block plus structural validation. This is
|
duplicates but shares the underlying stream/offset and may buffer beyond the
|
||||||
not cryptographic integrity — a determined attacker with write access
|
line, so callers should provide a descriptor dedicated to that password read.
|
||||||
can produce a tampered plaintext archive that passes the checksum
|
On POSIX, handled prompt interruptions restore the saved terminal state before
|
||||||
(XXH64 is not collision-resistant). It does catch accidental
|
termination; an exact-candidate PTY regression is required before release.
|
||||||
corruption and naive tampering.
|
|
||||||
|
Native private-key generation uses no-replace creation with POSIX mode `0600`
|
||||||
Use an encrypted mode if you need cryptographic integrity.
|
or a Windows current-user-only DACL. A failed write, flush/fsync, or close leaves
|
||||||
|
the incomplete or durability-uncertain exclusive file for manual review and
|
||||||
### 4. Authentication failure indistinguishability (F-11)
|
removal instead of risking an unlink-after-close race against a replacement
|
||||||
|
pathname. ZKEY and ZPQK inputs
|
||||||
The default error message for "wrong password", "wrong PQ key",
|
are accepted only after checksum, version, flags, reserved bytes, exact size,
|
||||||
and "actual header tamper" is the same single line:
|
and public/private role validation. This prevents role confusion and
|
||||||
|
partial/trailing-key acceptance; it does not protect a key after endpoint or
|
||||||
> `Error: Authentication failed (wrong key, wrong password, or tampered archive).`
|
account compromise.
|
||||||
|
|
||||||
This prevents an attacker who can issue extraction attempts from
|
### Encrypted archive integrity
|
||||||
learning which check failed first via the stderr output. Timing is
|
|
||||||
also constant (HMAC is always run, branchless return).
|
Current encrypted archives authenticate ciphertext, canonical block metadata,
|
||||||
|
and each frame's logical position. DATA and DEDUP_REF frames both receive this
|
||||||
The detailed cause is available via `--verbose` for debugging on
|
positional AAD. A reference is authenticated at its own position and carries
|
||||||
machines under the user's own control.
|
the authenticated source position needed to verify the referenced DATA frame,
|
||||||
|
so exchanging otherwise equivalent frames is not accepted.
|
||||||
### 5. Post-quantum forward secrecy (`--pq`, `--pq-only`, and optional `--pq-sdk`)
|
|
||||||
|
Current archives carry an archive-integrity trailer for global metadata. The
|
||||||
The native `--pq` mode uses ML-KEM-768 (FIPS 203 — validated byte-for-byte
|
`extract`, `list`, `test`, and `disk restore` paths refuse any no-AIT layout by
|
||||||
against OpenSSL 3.5's ML-KEM-768; see AUDIT.md) hybridized with X25519 via an
|
default without relying on an unauthenticated header flag.
|
||||||
HKDF combiner. Archives encrypted today cannot be decrypted by a future quantum
|
`--allow-legacy-no-ait` is a narrowly scoped, warning-producing recovery option
|
||||||
adversary holding only the ciphertext, assuming:
|
for those commands when the caller already trusts a pre-AIT archive. Selecting
|
||||||
|
it for attacker-controlled storage removes the header/footer authentication
|
||||||
- ML-KEM-768 retains its claimed security level (NIST Category 3,
|
assumption and is outside this threat model. `info` is an unauthenticated
|
||||||
192-bit classical / 96-bit quantum strength)
|
framing inspection that reports apparent AIT presence but validates neither the
|
||||||
- X25519 hybridization protects against an unforeseen ML-KEM break
|
trailer nor archive contents. These checks do not prevent deletion of the
|
||||||
- The recipient's private key is not later compromised
|
entire archive, rollback to an older valid archive, or storage-layer replay.
|
||||||
|
|
||||||
The native `--pq-only` mode (envelope type `0x06`) provides the same
|
Archive comments remain untrusted presentation data even when they are
|
||||||
harvest-now-decrypt-later protection using ML-KEM-768 as the *sole* key
|
authenticated. Display paths render control bytes without emitting raw terminal
|
||||||
mechanism. It exists for compliance postures that mandate a single
|
control sequences, limiting terminal-output injection while leaving the stored
|
||||||
NIST-standardised PQ primitive with no classical KEM in the envelope
|
and authenticated comment bytes unchanged.
|
||||||
(CNSA 2.0-style "PQ-only"). **The trade-off is a loss of the second
|
|
||||||
assumption above:** there is no X25519 hybridization, so an unforeseen
|
New 5.2.2 encrypted+dedup archives authenticate each reference offset. New
|
||||||
break of ML-KEM-768 alone is sufficient to recover the archive key. For
|
encrypted disk archives also authenticate an index that binds image size,
|
||||||
that reason `--pq` (hybrid) is the recommended default, and `--pq-only`
|
block count, and a chained XXH64 hash of the complete restored stream. The
|
||||||
should be used only when a policy forbids the classical component.
|
writer's additional SHA-256/128 comparison is only an in-memory collision guard
|
||||||
|
before deduplication; it is not an on-disk cryptographic hash. XXH64 is not
|
||||||
The optional `--pq-sdk` mode provides the same hybrid guarantee as
|
cryptographic, so a writer who controls a plain archive can recompute it.
|
||||||
`--pq` via the separately distributed SDK libraries.
|
|
||||||
|
Plain archives use non-cryptographic checksums. A writer who controls a plain
|
||||||
### 6. Side-channel resistance for cryptographic primitives
|
archive can recompute them.
|
||||||
|
|
||||||
The hot crypto paths (AES-256-CTR, HMAC-SHA256 comparison, X25519
|
### Native hybrid post-quantum mode
|
||||||
field operations, ML-KEM polynomial arithmetic) are implemented in
|
|
||||||
Jasmin and proved constant-time at the assembly level on x86_64.
|
The `--pq` mode combines an ML-KEM-768 shared secret and an X25519 shared secret
|
||||||
Non-Jasmin platforms (aarch64, fallback x86_64) use C implementations
|
as implemented in 5.2.2:
|
||||||
that avoid secret-dependent branches and memory accesses where
|
|
||||||
feasible — but without formal proof.
|
```text
|
||||||
|
hybrid_ikm = ml_ss XOR x25519_ss
|
||||||
---
|
archive_key = SHA3-512(hybrid_ikm || ml_ct || ephemeral_pk ||
|
||||||
|
"ZUPT-HYBRID-v1")
|
||||||
## What VaptVupt does NOT protect against
|
```
|
||||||
|
|
||||||
### 1. Compromised endpoints
|
Its goal is harvest-now/decrypt-later resistance if ML-KEM-768 remains secure,
|
||||||
|
with X25519 as a classical hedge under the combiner assumptions. This is not
|
||||||
VaptVupt cannot protect against:
|
session forward secrecy: compromise of the recipient's long-term private key
|
||||||
|
can compromise previously captured archives encrypted to it.
|
||||||
- Malware on the machine doing the encryption (it sees plaintext
|
|
||||||
before any crypto is applied)
|
The native `--pq-only` mode removes X25519 and derives a key from ML-KEM-768
|
||||||
- Malware on the machine doing the extraction (it sees plaintext
|
alone. Use it only when a policy specifically excludes the classical component;
|
||||||
after decryption)
|
it loses the hybrid hedge.
|
||||||
- A hardware keylogger capturing the password
|
|
||||||
- A compromised user account that can read your files or
|
The in-tree ML-KEM code has project tests, including known-answer vectors and a
|
||||||
`~/.zupt-key` directly
|
conditional OpenSSL 3.5 interoperability test. It has not been independently
|
||||||
- Cold-boot attacks on running machines
|
audited or formally verified as a whole implementation.
|
||||||
|
|
||||||
If you don't trust the machine, VaptVupt cannot help.
|
### Extraction containment
|
||||||
|
|
||||||
### 2. Key compromise
|
The reader rejects absolute paths, traversal components, control characters,
|
||||||
|
ambiguous trailing dot/space components, NTFS alternate-stream syntax, and
|
||||||
If the password or `~/.zupt-key` is leaked:
|
reserved Windows device names. POSIX extraction resolves every parent below a
|
||||||
|
pinned destination descriptor with no-follow operations after canonicalizing
|
||||||
- All archives encrypted with that key are decryptable
|
the user-selected root once. Windows extraction
|
||||||
- VaptVupt has no forward secrecy across archives — each archive
|
uses handle-relative traversal, rejects reparse-point parents, and publishes the
|
||||||
is encrypted under a single static key derived from the password
|
final name by handle without replacing an existing leaf. A checked path is not
|
||||||
or stored in the key file
|
re-resolved through a mutable parent.
|
||||||
- There is no key-rotation feature; rotate by re-encrypting
|
|
||||||
archives under a new password/key and securely deleting the old
|
Decoded bytes are first written to a private, exclusively created temporary
|
||||||
password/key
|
file. The final name is published only after the expected decoded size and
|
||||||
|
chained checksum match and the stream closes successfully; failures remove the
|
||||||
For high-value, long-term archives, treat the key file as you
|
temporary through its descriptor or handle. These controls reduce traversal,
|
||||||
would a master password: store it offline, encrypt it under
|
link, race, and partial-output risks, but do not establish that no parser or
|
||||||
another layer (e.g. on an encrypted USB), and rotate periodically.
|
filesystem bug can exist.
|
||||||
|
|
||||||
### 3. Password strength
|
The Windows handle-relative boundary in 5.2.2 covers normal local Win32 paths.
|
||||||
|
Win32 extended-length and device-namespace paths, raw UNC output roots, and
|
||||||
Password mode derives the key with PBKDF2-SHA256 (600k iterations)
|
mapped/network-drive output are not supported. Cross-build and Wine results are
|
||||||
in the default build, or Argon2id in a `make WITH_SDK=1` build. A
|
not a substitute for the required native `windows-latest` Unicode package
|
||||||
key derivation function slows offline guessing but does not make a
|
gate. Restore locally before moving verified output to network storage.
|
||||||
short, common password safe: a determined attacker with GPU clusters
|
|
||||||
or cloud compute can still exhaust a weak password.
|
Disk restore copies the measured compacted archive into one exclusively
|
||||||
|
created, auto-deleted scratch file before it opens a destructive destination.
|
||||||
Use a long, high-entropy password — a multi-word diceware passphrase
|
Preflight and restoration consume that same open snapshot. An explicit
|
||||||
or a random 16+ character string with a full alphabet. For critical
|
`ZUPT_TMPDIR` selects an existing scratch directory; failure there does not
|
||||||
data, use a key-file mode (native `--pq`, or the optional `--pq-sdk`
|
fall back to consuming the mutable source pathname. On supported Linux, macOS,
|
||||||
with a random key file from `vaptvupt keygen --sdk`) so the key is
|
and FreeBSD interfaces, a raw block-device target is rejected before writing if
|
||||||
CSPRNG output, not derived from human-typed text.
|
its capacity is unknown or smaller than the image. These controls reduce source
|
||||||
|
exchange and immediate overrun risk but do not protect against a compromised
|
||||||
### 4. Metadata leakage from archive structure
|
kernel/device, a wrongly selected sufficiently large device, power loss, or
|
||||||
|
hardware failure.
|
||||||
Even with encryption, an attacker who can see the archive bytes
|
|
||||||
can infer:
|
For an untrusted archive:
|
||||||
|
|
||||||
- Approximate file count (from `total_blocks` in the footer)
|
1. use a new empty destination outside sensitive trees;
|
||||||
- Total archive size (file size on disk)
|
2. run as a dedicated unprivileged user, never root;
|
||||||
- Whether the archive is encrypted at all (`ZUPT_FLAG_ENCRYPTED`
|
3. apply a container, sandbox, resource limits, and a storage quota when
|
||||||
in the global flags is visible)
|
available;
|
||||||
- Whether the archive is solid or per-file mode (visible flag)
|
4. inspect extracted paths, types, permissions, and content before moving them;
|
||||||
- Whether post-quantum mode is in use (visible flag)
|
5. never restore a disk image to a device without independently confirming both
|
||||||
- Approximate file size distribution (block sizes are visible
|
source and destination.
|
||||||
even when block payloads are encrypted)
|
|
||||||
- Archive creation time (a 64-bit timestamp in the header)
|
## Non-goals and residual risks
|
||||||
- A random 16-byte UUID per archive (no information leak, but
|
|
||||||
globally identifies the archive across copies)
|
ZUPT does not claim to provide:
|
||||||
|
|
||||||
If metadata privacy matters, layer VaptVupt under another tool that
|
- resistance to cache, power, EM, acoustic, speculative-execution, or all
|
||||||
hides bulk metadata (e.g., put the `.zupt` file inside a fixed-size
|
compiler-introduced timing side channels;
|
||||||
encrypted container).
|
- bounded resource consumption for every malformed archive;
|
||||||
|
- confidentiality of archive size or complete framing metadata;
|
||||||
### 5. Network attacks
|
- protection against compression-length oracles when secret and
|
||||||
|
attacker-controlled data are compressed together;
|
||||||
VaptVupt is not a network protocol. There is no:
|
- rollback detection across multiple valid versions of a backup;
|
||||||
|
- forward-secure sessions, remote authentication, replay protection, or secure
|
||||||
- Forward-secure session establishment (use TLS or Noise)
|
transport;
|
||||||
- Mutual authentication of remote parties (use signed messages or
|
- automatic key rotation, recovery, escrow, threshold access, or secure
|
||||||
TLS client certs)
|
deletion;
|
||||||
- Replay protection across sessions (archives can be replayed by
|
- preservation of every operating-system ACL, ownership attribute, extended
|
||||||
an attacker who can write to the destination)
|
attribute, or special-file semantic;
|
||||||
- Network-layer encryption (use TLS to transport `.zupt` files)
|
- safe operation on a compromised host.
|
||||||
|
|
||||||
### 6. Multi-party schemes
|
## Credential handling
|
||||||
|
|
||||||
There is no threshold cryptography, no n-of-m sharing, no
|
- Generate PQ keys on a trusted system using the OS CSPRNG.
|
||||||
multi-party computation, no proxy re-encryption. Each archive
|
- Keep private keys separate from the archive and from release/package inputs.
|
||||||
has exactly one decryption credential (one password OR one
|
- Store an offline recovery copy and test recovery before relying on a backup.
|
||||||
recipient key). To give two people access to the same archive,
|
- Use a distinct high-entropy credential where compromise isolation matters.
|
||||||
they must share the password or the key file.
|
- Re-encrypt under a new credential after suspected disclosure; there is no
|
||||||
|
in-place key rotation.
|
||||||
### 7. Plausible deniability / hidden volumes
|
- Never include credentials or sensitive archives in bug reports or CI logs.
|
||||||
|
|
||||||
VaptVupt archives have a fixed 6-byte magic `\x90\x5a\x55\x50\x54\x01`
|
## Supply-chain boundary
|
||||||
at offset 0. Anyone scanning the bytes can see it's a VaptVupt
|
|
||||||
archive. VaptVupt has no hidden-volume or duress-password feature.
|
Git and upstream source archives are source-only. They must pass
|
||||||
|
`scripts/check-source-only.sh` and must not contain executable code artifacts,
|
||||||
### 8. Side channels we don't claim to address
|
objects, shared/static libraries, distribution packages, unsafe symlinks, or Git
|
||||||
|
LFS pointers.
|
||||||
- Power analysis (relevant for embedded targets, not commodity desktops)
|
|
||||||
- Electromagnetic emanation
|
Nested inspection is itself an untrusted-input boundary. The release scanner
|
||||||
- Acoustic side channels
|
must cap recursion depth, archive members, per-entry expansion, and total
|
||||||
- Network timing of upload patterns
|
expanded bytes and fail closed when a cap is reached. Scanner bomb regressions
|
||||||
- Filesystem-level metadata (mtime/atime of the `.zupt` file)
|
and all other late self-audit fixes are pending until rerun on the exact
|
||||||
|
candidate.
|
||||||
### 9. Trusted setup of post-quantum primitives
|
|
||||||
|
DEB, binary RPM, SRPM, notice-bearing Linux tar.xz, source-only portable GUI
|
||||||
The in-tree ML-KEM-768 implementation was not independently audited
|
ZIP, Windows ZIP, and macOS DMG files can be published separately from the
|
||||||
at the time of writing. We use NIST KAT vectors for correctness
|
tagged source. Each artifact extends the trust boundary to its builder,
|
||||||
verification but have not formally proven constant-time properties
|
toolchain, runner image, and packaging scripts. Treat it as validated only when
|
||||||
for every PQ code path.
|
the exact target has a recorded build, content/package inspection, extracted or
|
||||||
|
installed smoke test, and applicable archive round trip. An AppImage is not
|
||||||
For maximum assurance, treat the post-quantum layer as a hedge — it
|
promoted for 5.2.2; bare Linux and Windows executables are also excluded.
|
||||||
does not replace the X25519 layer; both must be broken for an
|
|
||||||
attacker to recover plaintext.
|
For 5.2.2, that gated artifact scope covers the CLI files plus the exact GUI
|
||||||
|
DEB, noarch/source RPM, and source-only portable ZIP named in the README. The
|
||||||
### 10. Format extension attacks
|
portable ZIP contains no compiled runtime and crosses the release boundary only
|
||||||
|
after source scans and an exact safe-member check. AppDir and Flatpak bundles
|
||||||
The format is versioned (v1.6). Older readers may accept newer
|
and GUI platform installers remain excluded; Windows ZIP and macOS DMG outputs
|
||||||
archives in unexpected ways. We try to maintain forward
|
remain CLI-only.
|
||||||
compatibility, but a careful attacker who can produce
|
|
||||||
malformed-but-just-valid archives may find parser-state issues that
|
## Historical compatibility notes
|
||||||
don't rise to the level of a CVE. The fuzzing harness
|
|
||||||
(`make fuzz-format`) is the primary mitigation; report bugs.
|
These are historical facts about earlier releases, retained to support recovery:
|
||||||
|
|
||||||
### 11. Compression-side-channel attacks (CRIME / BREACH style)
|
- Releases through 4.1.0 could reuse an AES-CTR nonce in encrypted `--dedup`
|
||||||
|
archives. Release 4.2.0 changed to fresh random per-block nonces. Re-encrypt
|
||||||
VaptVupt compresses before encryption. If an attacker can:
|
affected older archives.
|
||||||
|
- Releases through 4.2.1 used round-3 CRYSTALS-Kyber semantics in the native PQ
|
||||||
- Influence part of the plaintext (e.g. inject a known prefix)
|
path. Release 5.0.0 corrected the implementation to FIPS 203 ML-KEM-768,
|
||||||
- Observe the resulting archive size precisely
|
changing native PQ key/archive compatibility. See `CHANGELOG.md` before
|
||||||
|
planning cross-version restoration.
|
||||||
then they can use the compression ratio to learn information about
|
- Pre-AIT archive layouts now fail closed by default. The explicit
|
||||||
the rest of the plaintext — this is the classic CRIME/BREACH attack
|
`--allow-legacy-no-ait` read option is only for recovery from a known, trusted
|
||||||
against TLS compression.
|
historical archive and leaves its header/footer metadata outside the current
|
||||||
|
authenticated boundary.
|
||||||
VaptVupt is designed for offline backup, where attacker-controlled
|
- The 5.2.2 reader retains compatibility parsers for the fixed-width disk index
|
||||||
plaintext injection is rare. If your threat model includes
|
and encrypted-dedup linear AAD sequence published through 5.2.1. An actual
|
||||||
attacker-chosen plaintext mixed with secret plaintext in the same
|
v5.2.1 password-encrypted DATA/DATA/REF/DATA disk fixture is stored as hexadecimal
|
||||||
archive, use `--no-compress` (codec 0 = STORE) to disable the
|
text with source and hash provenance. The candidate lists, tests, extracts, and restores
|
||||||
LZ codec and eliminate this side channel.
|
it byte-exact, with a warning that the legacy index has no whole-image hash;
|
||||||
|
the exact final candidate must repeat that gate. Older readers are not
|
||||||
---
|
claimed to accept new flag-gated 5.2.2 records, and untested historical mode
|
||||||
|
combinations remain unclaimed.
|
||||||
## Cryptographic assumptions
|
|
||||||
|
Historical test counts in the changelog describe those releases. They do not
|
||||||
VaptVupt's security rests on the following standard assumptions:
|
automatically become 5.2.2 results; current outcomes belong in the release
|
||||||
|
validation record, with unavailable environments marked `SKIP`. In particular,
|
||||||
| Assumption | What breaks if it fails |
|
runs made before the final positional-AAD and mandatory-AIT changes are not
|
||||||
|---|---|
|
final release gates for the resulting candidate.
|
||||||
| AES-256-CTR is a secure stream cipher | All encrypted archives become readable |
|
|
||||||
| HMAC-SHA256 is a secure PRF / MAC | Tamper detection fails; integrity can be forged |
|
|
||||||
| PBKDF2-SHA256 (or Argon2id, WITH_SDK) is a secure password KDF | Password-mode archives become brute-forceable faster |
|
|
||||||
| ML-KEM-768 retains NIST Category 3 security | `--pq` / `--pq-sdk` reduce to the X25519 layer; **`--pq-only` has no fallback and is broken** |
|
|
||||||
| X25519 retains 128-bit security (no quantum) | Hybrid PQ modes reduce to the ML-KEM layer; `--pq-only` and classical password mode unaffected |
|
|
||||||
| HKDF-SHA256 is a secure key-derivation construction | Combined PQ + classical keys may be predictable |
|
|
||||||
| SHA3 / SHAKE retain pre-image and collision resistance | Auxiliary protocol bindings may be forged |
|
|
||||||
|
|
||||||
If you don't trust one of these primitives, VaptVupt cannot protect
|
|
||||||
you. We rely on the same primitives the broader cryptographic
|
|
||||||
community has standardized.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Reporting security issues
|
## Reporting security issues
|
||||||
|
|
||||||
Email `sac@securityops.co` with the subject `VaptVupt security report`.
|
Email **zupt@riseup.net** with `[security]` in the subject. Include the version,
|
||||||
PGP key available on request.
|
platform, impact, and a minimal non-sensitive reproducer. Do not disclose the
|
||||||
|
issue publicly until a coordinated timeline has been agreed.
|
||||||
|
|
||||||
We will:
|
Document version: 5.2.2, 2026-08-31.
|
||||||
|
|
||||||
- Acknowledge receipt within 7 days
|
|
||||||
- Investigate and publish a CVE / advisory if warranted
|
|
||||||
- Credit you in the CHANGELOG if you wish
|
|
||||||
|
|
||||||
Please don't open public issues for security reports until we've
|
|
||||||
coordinated disclosure. For non-security bugs (parser edge cases,
|
|
||||||
documentation typos, performance issues), open a public issue
|
|
||||||
normally.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Document version
|
|
||||||
|
|
||||||
This threat model covers archive format v1.6 as shipped in VaptVupt
|
|
||||||
5.0.0. It is part of the source tree (`THREAT_MODEL.md`) and
|
|
||||||
versioned with the project; this section will be updated as the
|
|
||||||
format evolves.
|
|
||||||
|
|
|
||||||
21
build.bat
21
build.bat
|
|
@ -1,21 +0,0 @@
|
||||||
@echo off
|
|
||||||
echo Zupt v0.4 Build Script for Windows
|
|
||||||
where gcc >nul 2>nul
|
|
||||||
if %ERRORLEVEL% EQU 0 (
|
|
||||||
gcc -Wall -Wextra -O2 -std=c11 -Iinclude ^
|
|
||||||
src\zupt_main.c src\zupt_format.c src\zupt_lz.c src\zupt_lzh.c src\zupt_xxh.c ^
|
|
||||||
src\zupt_sha256.c src\zupt_aes256.c src\zupt_crypto.c src\zupt_predict.c ^
|
|
||||||
-lm -o zupt.exe
|
|
||||||
if %ERRORLEVEL% EQU 0 (echo [OK] zupt.exe) else (echo [FAIL])
|
|
||||||
exit /b %ERRORLEVEL%
|
|
||||||
)
|
|
||||||
where cl >nul 2>nul
|
|
||||||
if %ERRORLEVEL% EQU 0 (
|
|
||||||
cl /nologo /W4 /O2 /Iinclude /D_CRT_SECURE_NO_WARNINGS ^
|
|
||||||
src\zupt_main.c src\zupt_format.c src\zupt_lz.c src\zupt_lzh.c src\zupt_xxh.c ^
|
|
||||||
src\zupt_sha256.c src\zupt_aes256.c src\zupt_crypto.c src\zupt_predict.c ^
|
|
||||||
/Fe:zupt.exe & del *.obj 2>nul
|
|
||||||
exit /b 0
|
|
||||||
)
|
|
||||||
echo No C compiler found.
|
|
||||||
exit /b 1
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
#compdef vaptvupt zupt
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
|
||||||
#
|
|
||||||
# Install:
|
|
||||||
# sudo install -m 644 completions/_zupt /usr/share/zsh/site-functions/_zupt
|
|
||||||
# or for a single user (anywhere in $fpath):
|
|
||||||
# cp completions/_zupt ~/.zsh/completion/_zupt
|
|
||||||
# # then in ~/.zshrc:
|
|
||||||
# # fpath=(~/.zsh/completion $fpath)
|
|
||||||
# # autoload -U compinit && compinit
|
|
||||||
|
|
||||||
_zupt_levels() {
|
|
||||||
_values 'compression level' \
|
|
||||||
'1[fastest, smallest window]' \
|
|
||||||
'2[fast]' \
|
|
||||||
'3[balanced (low)]' \
|
|
||||||
'4[balanced]' \
|
|
||||||
'5[balanced (high)]' \
|
|
||||||
'6[high compression]' \
|
|
||||||
'7[default; high]' \
|
|
||||||
'8[maximum, 1MB window]' \
|
|
||||||
'9[maximum, deep search]'
|
|
||||||
}
|
|
||||||
|
|
||||||
_zupt_kdf() {
|
|
||||||
_values 'KDF' \
|
|
||||||
'argon2id[memory-hard, default since v2.4.1]' \
|
|
||||||
'pbkdf2[legacy 600k-iter PBKDF2-SHA256]'
|
|
||||||
}
|
|
||||||
|
|
||||||
_zupt_threads() {
|
|
||||||
_values 'threads' '0[auto]' '1' '2' '4' '8' '16' '32' '64'
|
|
||||||
}
|
|
||||||
|
|
||||||
_zupt_compress_opts() {
|
|
||||||
_arguments \
|
|
||||||
'(-l --level)'{-l,--level}'[compression level]:level:_zupt_levels' \
|
|
||||||
'(-b --block)'{-b,--block}'[block size in bytes]:size:' \
|
|
||||||
'(-s --store)'{-s,--store}'[store without compression]' \
|
|
||||||
'(-f --fast)'{-f,--fast}'[use fast LZ codec]' \
|
|
||||||
'(--vv --vaptvupt)'{--vv,--vaptvupt}'[use VaptVupt codec]' \
|
|
||||||
'--lzhp[use Zupt-LZHP codec (LZ77+Huffman, no SIMD)]' \
|
|
||||||
'(-p --password)'{-p,--password}'[encrypt with password]:password:' \
|
|
||||||
'--kdf[password KDF]:kdf:_zupt_kdf' \
|
|
||||||
'(-c --comment)'{-c,--comment}'[embed archive comment]:text:' \
|
|
||||||
'--comment-file[read comment from file]:file:_files' \
|
|
||||||
'--pq[legacy PQ encryption]:pubkey:_files' \
|
|
||||||
'--pq-sdk[PQ encryption via libzuptsdk]:pubkey:_files' \
|
|
||||||
'(-D --dedup)'{-D,--dedup}'[block-level deduplication]' \
|
|
||||||
'--solid[solid mode: single stream]' \
|
|
||||||
'(-v --verbose)'{-v,--verbose}'[verbose output]' \
|
|
||||||
'(-q --quiet)'{-q,--quiet}'[suppress non-error output]' \
|
|
||||||
'(-t --threads)'{-t,--threads}'[thread count]:threads:_zupt_threads' \
|
|
||||||
'*:files:_files'
|
|
||||||
}
|
|
||||||
|
|
||||||
_zupt_extract_opts() {
|
|
||||||
_arguments \
|
|
||||||
'(-o --output)'{-o,--output}'[output directory]:directory:_directories' \
|
|
||||||
'(-p --password)'{-p,--password}'[decryption password]:password:' \
|
|
||||||
'--pq[legacy PQ decryption]:privkey:_files' \
|
|
||||||
'--pq-sdk[PQ decryption via libzuptsdk]:privkey:_files' \
|
|
||||||
'(-v --verbose)'{-v,--verbose}'[verbose output]' \
|
|
||||||
'(-t --threads)'{-t,--threads}'[thread count]:threads:_zupt_threads' \
|
|
||||||
'*:archive:_files -g "*.zupt"'
|
|
||||||
}
|
|
||||||
|
|
||||||
_zupt() {
|
|
||||||
local context curcontext="$curcontext" state line
|
|
||||||
local -a subcommands
|
|
||||||
|
|
||||||
subcommands=(
|
|
||||||
'compress:create an archive'
|
|
||||||
'c:create an archive (alias)'
|
|
||||||
'extract:extract an archive'
|
|
||||||
'x:extract an archive (alias)'
|
|
||||||
'list:list archive entries'
|
|
||||||
'l:list archive entries (alias)'
|
|
||||||
'test:verify archive integrity'
|
|
||||||
't:verify archive integrity (alias)'
|
|
||||||
'info:archive metadata (no key needed)'
|
|
||||||
'bench:benchmark levels 1-9'
|
|
||||||
'disk:full-disk backup/restore'
|
|
||||||
'keygen:generate a key file'
|
|
||||||
'version:print version info'
|
|
||||||
'help:print help'
|
|
||||||
)
|
|
||||||
|
|
||||||
_arguments -C \
|
|
||||||
'(-): :->command' \
|
|
||||||
'(-)*:: :->args'
|
|
||||||
|
|
||||||
case $state in
|
|
||||||
command)
|
|
||||||
_describe -t commands 'zupt subcommand' subcommands
|
|
||||||
;;
|
|
||||||
args)
|
|
||||||
case $line[1] in
|
|
||||||
compress|c)
|
|
||||||
_zupt_compress_opts
|
|
||||||
;;
|
|
||||||
extract|x)
|
|
||||||
_zupt_extract_opts
|
|
||||||
;;
|
|
||||||
list|l|test|t)
|
|
||||||
_arguments \
|
|
||||||
'(-p --password)'{-p,--password}'[password]:password:' \
|
|
||||||
'--pq[legacy PQ privkey]:privkey:_files' \
|
|
||||||
'--pq-sdk[PQ privkey]:privkey:_files' \
|
|
||||||
'(-v --verbose)'{-v,--verbose}'[verbose]' \
|
|
||||||
'*:archive:_files -g "*.zupt"'
|
|
||||||
;;
|
|
||||||
info)
|
|
||||||
_arguments '*:archive:_files -g "*.zupt"'
|
|
||||||
;;
|
|
||||||
disk)
|
|
||||||
_values 'disk action' 'backup' 'restore'
|
|
||||||
;;
|
|
||||||
keygen)
|
|
||||||
_arguments \
|
|
||||||
'--sdk[generate SDK v2 keypair]' \
|
|
||||||
'--pq-sdk[same as --sdk]' \
|
|
||||||
'-o[output keyfile]:file:_files' \
|
|
||||||
'--pub[export public key from -k]' \
|
|
||||||
'-k[source private key for --pub]:file:_files'
|
|
||||||
;;
|
|
||||||
bench)
|
|
||||||
_arguments '*:files:_files'
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
_zupt "$@"
|
|
||||||
132
completions/_zupt
Normal file
132
completions/_zupt
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
#compdef zupt
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
local context state state_descr line
|
||||||
|
local -a _zupt_disk_legacy_options
|
||||||
|
typeset -A opt_args
|
||||||
|
|
||||||
|
_zupt_password_options=(
|
||||||
|
'(-p --password)'{-p,--password}'[password in process arguments]:password:'
|
||||||
|
'--password-prompt[read password interactively without echo]'
|
||||||
|
'--pass-file[read password from first line of file]:password file:_files'
|
||||||
|
'--pass-fd[read password from inherited file descriptor]:file descriptor:'
|
||||||
|
)
|
||||||
|
|
||||||
|
_zupt_pq_options=(
|
||||||
|
'--pq[native ML-KEM-768 + X25519 hybrid key]:key file:_files'
|
||||||
|
'--pq-only[native ML-KEM-768-only key]:key file:_files'
|
||||||
|
'--pq-sdk[optional system libvuptsdk key]:key file:_files'
|
||||||
|
'--pq-box[optional system libpqvaptvupt key]:key file:_files'
|
||||||
|
)
|
||||||
|
|
||||||
|
_zupt_read_options=(
|
||||||
|
"${_zupt_password_options[@]}"
|
||||||
|
"${_zupt_pq_options[@]}"
|
||||||
|
'(-v --verbose)'{-v,--verbose}'[additional diagnostics]'
|
||||||
|
'--allow-legacy-no-ait[recover a trusted old archive without an integrity trailer]'
|
||||||
|
)
|
||||||
|
|
||||||
|
_arguments -C \
|
||||||
|
'1:command:->command' \
|
||||||
|
'*::argument:->arguments'
|
||||||
|
|
||||||
|
case $state in
|
||||||
|
command)
|
||||||
|
_values 'command' \
|
||||||
|
'compress:create an archive' 'c:create an archive' \
|
||||||
|
'extract:extract an archive' 'x:extract an archive' \
|
||||||
|
'list:list archive entries' 'l:list archive entries' \
|
||||||
|
'test:verify archive integrity' 't:verify archive integrity' \
|
||||||
|
'info:show framing metadata' 'i:show framing metadata' \
|
||||||
|
'bench:benchmark compression levels' 'b:benchmark compression levels' \
|
||||||
|
'disk:back up or restore a disk image' \
|
||||||
|
'keygen:generate or export a recipient key' \
|
||||||
|
'version:show version and build information' \
|
||||||
|
'help:show command help' '--version:show version and build information' \
|
||||||
|
'-V:show version and build information' '--help:show command help' \
|
||||||
|
'-h:show command help'
|
||||||
|
;;
|
||||||
|
arguments)
|
||||||
|
case ${line[1]} in
|
||||||
|
compress|c)
|
||||||
|
_arguments \
|
||||||
|
'(-l --level)'{-l,--level}'[compression level]:level:(1 2 3 4 5 6 7 8 9)' \
|
||||||
|
'(-b --block)'{-b,--block}'[block size in bytes]:bytes:' \
|
||||||
|
'(-s --store)'{-s,--store}'[store without compression]' \
|
||||||
|
'(-f --fast)'{-f,--fast}'[use fast LZ codec]' \
|
||||||
|
'(--vv --vaptvupt)'{--vv,--vaptvupt}'[force VaptVupt LZ + ANS codec]' \
|
||||||
|
'--lzhp[force portable LZHP codec]' \
|
||||||
|
"${_zupt_password_options[@]}" \
|
||||||
|
'--kdf[password KDF]:KDF:(pbkdf2 argon2id)' \
|
||||||
|
'(-c --comment)'{-c,--comment}'[archive comment]:comment:' \
|
||||||
|
'--comment-file[read archive comment from file]:comment file:_files' \
|
||||||
|
"${_zupt_pq_options[@]}" \
|
||||||
|
'(-D --dedup)'{-D,--dedup}'[block-level deduplication]' \
|
||||||
|
'(-S --solid)'{-S,--solid}'[solid single-stream compression]' \
|
||||||
|
'(-y --force)'{-y,--force}'[overwrite an existing non-.zupt output]' \
|
||||||
|
'(-v --verbose)'{-v,--verbose}'[additional progress output]' \
|
||||||
|
'(-t --threads)'{-t,--threads}'[compression thread count]:threads:' \
|
||||||
|
'1:output archive:_files -g "*.zupt"' \
|
||||||
|
'*:input file or directory:_files'
|
||||||
|
;;
|
||||||
|
extract|x)
|
||||||
|
_arguments \
|
||||||
|
'(-o --output)'{-o,--output}'[output directory]:directory:_directories' \
|
||||||
|
"${_zupt_read_options[@]}" \
|
||||||
|
'(-t --threads)'{-t,--threads}'[decompression thread count]:threads:' \
|
||||||
|
'1:archive:_files -g "*.zupt"'
|
||||||
|
;;
|
||||||
|
list|l|test|t)
|
||||||
|
_arguments \
|
||||||
|
"${_zupt_read_options[@]}" \
|
||||||
|
'1:archive:_files -g "*.zupt"'
|
||||||
|
;;
|
||||||
|
info|i)
|
||||||
|
_arguments '1:archive:_files -g "*.zupt"'
|
||||||
|
;;
|
||||||
|
bench|b)
|
||||||
|
_arguments '--compare[compare available external compressors]' \
|
||||||
|
'*:input file or directory:_files'
|
||||||
|
;;
|
||||||
|
disk)
|
||||||
|
_zupt_disk_legacy_options=()
|
||||||
|
if [[ ${line[2]-} == restore ]]; then
|
||||||
|
_zupt_disk_legacy_options=(
|
||||||
|
'--allow-legacy-no-ait[recover a trusted old disk archive without an integrity trailer]'
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
_arguments -C \
|
||||||
|
'1:disk command:(backup restore)' \
|
||||||
|
'(-l --level)'{-l,--level}'[compression level]:level:(1 2 3 4 5 6 7 8 9)' \
|
||||||
|
'(-b --block)'{-b,--block}'[block size in bytes]:bytes:' \
|
||||||
|
'(-s --store)'{-s,--store}'[store without compression]' \
|
||||||
|
'(--vv --vaptvupt)'{--vv,--vaptvupt}'[force VaptVupt LZ + ANS codec]' \
|
||||||
|
'--lzhp[force portable LZHP codec]' \
|
||||||
|
"${_zupt_password_options[@]}" \
|
||||||
|
'--kdf[password KDF]:KDF:(pbkdf2 argon2id)' \
|
||||||
|
'(-c --comment)'{-c,--comment}'[archive comment]:comment:' \
|
||||||
|
'--comment-file[read archive comment from file]:comment file:_files' \
|
||||||
|
'--pq[native hybrid key]:key file:_files' \
|
||||||
|
'--pq-only[native ML-KEM-768-only key]:key file:_files' \
|
||||||
|
'(-D --dedup)'{-D,--dedup}'[block-level deduplication]' \
|
||||||
|
'(-v --verbose)'{-v,--verbose}'[additional progress output]' \
|
||||||
|
'(-t --threads)'{-t,--threads}'[thread count]:threads:' \
|
||||||
|
"${_zupt_disk_legacy_options[@]}" \
|
||||||
|
'2:archive:_files' \
|
||||||
|
'3:device or file:_files'
|
||||||
|
;;
|
||||||
|
keygen)
|
||||||
|
_arguments \
|
||||||
|
'(-o --output)'{-o,--output}'[output key file]:output file:_files' \
|
||||||
|
'--pub[export a public key]' \
|
||||||
|
'(-k --key)'{-k,--key}'[source private key]:private key:_files' \
|
||||||
|
'(--pq-only --pqonly)'{--pq-only,--pqonly}'[native ML-KEM-768-only key format]' \
|
||||||
|
'(--sdk --pq-sdk)'{--sdk,--pq-sdk}'[optional system libvuptsdk key format]' \
|
||||||
|
'(--box --pq-box)'{--box,--pq-box}'[optional system libpqvaptvupt key format]'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
unset _zupt_password_options _zupt_pq_options _zupt_read_options
|
||||||
|
unset _zupt_disk_legacy_options
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
# bash completion for vaptvupt (with `zupt` legacy alias)
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
|
||||||
#
|
|
||||||
# Install (system-wide):
|
|
||||||
# sudo install -m 644 completions/vaptvupt.bash /usr/share/bash-completion/completions/vaptvupt
|
|
||||||
# sudo ln -sf vaptvupt /usr/share/bash-completion/completions/zupt
|
|
||||||
# or for a single user:
|
|
||||||
# cp completions/vaptvupt.bash ~/.local/share/bash-completion/completions/vaptvupt
|
|
||||||
#
|
|
||||||
# Reload your shell or `source` the file to pick up changes.
|
|
||||||
|
|
||||||
_vaptvupt() {
|
|
||||||
local cur prev words cword
|
|
||||||
_init_completion -n = 2>/dev/null || {
|
|
||||||
# _init_completion missing on this host; fall back to manual setup.
|
|
||||||
local IFS=$' \t\n'
|
|
||||||
COMPREPLY=()
|
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
||||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
||||||
cword=$COMP_CWORD
|
|
||||||
words=("${COMP_WORDS[@]}")
|
|
||||||
}
|
|
||||||
|
|
||||||
local subcommands="compress c extract x list l test t info bench disk keygen version help"
|
|
||||||
local global_opts="-v --verbose -q --quiet -t --threads -h --help"
|
|
||||||
|
|
||||||
# First positional → subcommand
|
|
||||||
if [ "$cword" -eq 1 ]; then
|
|
||||||
COMPREPLY=( $(compgen -W "$subcommands" -- "$cur") )
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local subcmd="${words[1]}"
|
|
||||||
|
|
||||||
case "$prev" in
|
|
||||||
-p|--password)
|
|
||||||
# Don't complete passwords from filesystem
|
|
||||||
COMPREPLY=()
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
-l|--level)
|
|
||||||
COMPREPLY=( $(compgen -W "1 2 3 4 5 6 7 8 9" -- "$cur") )
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
--kdf)
|
|
||||||
COMPREPLY=( $(compgen -W "argon2id pbkdf2" -- "$cur") )
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
-t|--threads)
|
|
||||||
COMPREPLY=( $(compgen -W "0 1 2 4 8 16 32" -- "$cur") )
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
-b|--block)
|
|
||||||
COMPREPLY=( $(compgen -W "65536 131072 262144 524288 1048576" -- "$cur") )
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
-o|--output)
|
|
||||||
_filedir -d
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
--pq|--pq-sdk)
|
|
||||||
# Key files (no extension constraint)
|
|
||||||
_filedir
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
--comment-file)
|
|
||||||
_filedir
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
-c|--comment)
|
|
||||||
# Free-form text; no useful completion
|
|
||||||
COMPREPLY=()
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
-k)
|
|
||||||
_filedir
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
case "$subcmd" in
|
|
||||||
compress|c)
|
|
||||||
if [[ "$cur" == -* ]]; then
|
|
||||||
COMPREPLY=( $(compgen -W "
|
|
||||||
-l --level -b --block -s --store -f --fast
|
|
||||||
--vv --vaptvupt --lzhp
|
|
||||||
-p --password --kdf
|
|
||||||
-c --comment --comment-file
|
|
||||||
--pq --pq-sdk
|
|
||||||
--dedup -D --solid
|
|
||||||
-v --verbose -q --quiet -t --threads
|
|
||||||
$global_opts
|
|
||||||
" -- "$cur") )
|
|
||||||
else
|
|
||||||
_filedir
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
extract|x)
|
|
||||||
if [[ "$cur" == -* ]]; then
|
|
||||||
COMPREPLY=( $(compgen -W "
|
|
||||||
-o --output -p --password
|
|
||||||
--pq --pq-sdk
|
|
||||||
-v --verbose -t --threads
|
|
||||||
$global_opts
|
|
||||||
" -- "$cur") )
|
|
||||||
else
|
|
||||||
_filedir 'zupt'
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
list|l|test|t)
|
|
||||||
if [[ "$cur" == -* ]]; then
|
|
||||||
COMPREPLY=( $(compgen -W "
|
|
||||||
-p --password --pq --pq-sdk
|
|
||||||
-v --verbose
|
|
||||||
$global_opts
|
|
||||||
" -- "$cur") )
|
|
||||||
else
|
|
||||||
_filedir 'zupt'
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
info)
|
|
||||||
_filedir 'zupt'
|
|
||||||
;;
|
|
||||||
disk)
|
|
||||||
if [ "$cword" -eq 2 ]; then
|
|
||||||
COMPREPLY=( $(compgen -W "backup restore" -- "$cur") )
|
|
||||||
elif [[ "$cur" == -* ]]; then
|
|
||||||
COMPREPLY=( $(compgen -W "
|
|
||||||
-p --password --pq --pq-sdk
|
|
||||||
--kdf -c --comment --comment-file
|
|
||||||
-v --verbose
|
|
||||||
" -- "$cur") )
|
|
||||||
else
|
|
||||||
_filedir
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
keygen)
|
|
||||||
if [[ "$cur" == -* ]]; then
|
|
||||||
COMPREPLY=( $(compgen -W "--sdk --pq-sdk -o --pub -k" -- "$cur") )
|
|
||||||
else
|
|
||||||
_filedir
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
bench)
|
|
||||||
_filedir
|
|
||||||
;;
|
|
||||||
version|help)
|
|
||||||
COMPREPLY=()
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
_filedir
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
complete -F _vaptvupt vaptvupt
|
|
||||||
# v3.0.0: legacy `zupt` name retained as an alias.
|
|
||||||
complete -F _vaptvupt zupt
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
# Fish completions for zupt
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
|
||||||
#
|
|
||||||
# Install:
|
|
||||||
# sudo install -m 644 completions/zupt.fish /usr/share/fish/vendor_completions.d/
|
|
||||||
# or for a single user:
|
|
||||||
# cp completions/zupt.fish ~/.config/fish/completions/
|
|
||||||
|
|
||||||
# ─── Subcommands ───
|
|
||||||
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'compress c' -d 'Create an archive'
|
|
||||||
complete -c zupt -f -n '__fish_use_subcommand' -a 'compress c' -d 'Create an archive'
|
|
||||||
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'extract x' -d 'Extract an archive'
|
|
||||||
complete -c zupt -f -n '__fish_use_subcommand' -a 'extract x' -d 'Extract an archive'
|
|
||||||
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'list l' -d 'List archive entries'
|
|
||||||
complete -c zupt -f -n '__fish_use_subcommand' -a 'list l' -d 'List archive entries'
|
|
||||||
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'test t' -d 'Verify archive integrity'
|
|
||||||
complete -c zupt -f -n '__fish_use_subcommand' -a 'test t' -d 'Verify archive integrity'
|
|
||||||
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'info' -d 'Archive metadata (no key needed)'
|
|
||||||
complete -c zupt -f -n '__fish_use_subcommand' -a 'info' -d 'Archive metadata (no key needed)'
|
|
||||||
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'bench' -d 'Benchmark compression levels'
|
|
||||||
complete -c zupt -f -n '__fish_use_subcommand' -a 'bench' -d 'Benchmark compression levels'
|
|
||||||
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'disk' -d 'Full-disk backup/restore'
|
|
||||||
complete -c zupt -f -n '__fish_use_subcommand' -a 'disk' -d 'Full-disk backup/restore'
|
|
||||||
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'keygen' -d 'Generate a key file'
|
|
||||||
complete -c zupt -f -n '__fish_use_subcommand' -a 'keygen' -d 'Generate a key file'
|
|
||||||
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'version' -d 'Print version info'
|
|
||||||
complete -c zupt -f -n '__fish_use_subcommand' -a 'version' -d 'Print version info'
|
|
||||||
complete -c vaptvupt -f -n '__fish_use_subcommand' -a 'help' -d 'Print help'
|
|
||||||
complete -c zupt -f -n '__fish_use_subcommand' -a 'help' -d 'Print help'
|
|
||||||
|
|
||||||
# Helper predicates
|
|
||||||
function __fish_zupt_using_subcommand
|
|
||||||
set -l cmd (commandline -opc)
|
|
||||||
if test (count $cmd) -gt 1
|
|
||||||
contains -- $cmd[2] $argv
|
|
||||||
return $status
|
|
||||||
end
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
|
|
||||||
# ─── Compress options ───
|
|
||||||
set -l compress_cmds compress c
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s l -l level -d 'Compression level (1-9, default 7)' -x -a '1 2 3 4 5 6 7 8 9'
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s l -l level -d 'Compression level (1-9, default 7)' -x -a '1 2 3 4 5 6 7 8 9'
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s b -l block -d 'Block size in bytes' -x
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s b -l block -d 'Block size in bytes' -x
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s s -l store -d 'Store without compression'
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s s -l store -d 'Store without compression'
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s f -l fast -d 'Use fast LZ codec'
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s f -l fast -d 'Use fast LZ codec'
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -l vv -l vaptvupt -d 'Use VaptVupt codec'
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -l vv -l vaptvupt -d 'Use VaptVupt codec'
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -l lzhp -d 'Use Zupt-LZHP codec (no SIMD needed)'
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -l lzhp -d 'Use Zupt-LZHP codec (no SIMD needed)'
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s p -l password -d 'Encrypt with password (prompted if empty)' -x
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s p -l password -d 'Encrypt with password (prompted if empty)' -x
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -l kdf -d 'Password KDF' -x -a 'argon2id pbkdf2'
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -l kdf -d 'Password KDF' -x -a 'argon2id pbkdf2'
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s c -l comment -d 'Embed archive comment (UTF-8, ≤4096 B)' -x
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s c -l comment -d 'Embed archive comment (UTF-8, ≤4096 B)' -x
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -l comment-file -d 'Read comment from file' -r
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -l comment-file -d 'Read comment from file' -r
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -l pq -d 'Legacy PQ public key' -r
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -l pq -d 'Legacy PQ public key' -r
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -l pq-sdk -d 'PQ public key (libzuptsdk)' -r
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -l pq-sdk -d 'PQ public key (libzuptsdk)' -r
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s D -l dedup -d 'Block-level deduplication'
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s D -l dedup -d 'Block-level deduplication'
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -l solid -d 'Solid mode (single stream)'
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -l solid -d 'Solid mode (single stream)'
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s v -l verbose -d 'Verbose output'
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s v -l verbose -d 'Verbose output'
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s q -l quiet -d 'Suppress non-error output'
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s q -l quiet -d 'Suppress non-error output'
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $compress_cmds" -s t -l threads -d 'Thread count (0=auto)' -x
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $compress_cmds" -s t -l threads -d 'Thread count (0=auto)' -x
|
|
||||||
|
|
||||||
# ─── Extract / List / Test options ───
|
|
||||||
set -l rw_cmds extract x list l test t
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $rw_cmds" -s o -l output -d 'Output directory' -x -a '(__fish_complete_directories)'
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $rw_cmds" -s o -l output -d 'Output directory' -x -a '(__fish_complete_directories)'
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $rw_cmds" -s p -l password -d 'Decryption password' -x
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $rw_cmds" -s p -l password -d 'Decryption password' -x
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $rw_cmds" -l pq -d 'Legacy PQ private key' -r
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $rw_cmds" -l pq -d 'Legacy PQ private key' -r
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $rw_cmds" -l pq-sdk -d 'PQ private key (libzuptsdk)' -r
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $rw_cmds" -l pq-sdk -d 'PQ private key (libzuptsdk)' -r
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $rw_cmds" -s v -l verbose -d 'Verbose output (surfaces top-MAC/SDK details on failure)'
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $rw_cmds" -s v -l verbose -d 'Verbose output (surfaces top-MAC/SDK details on failure)'
|
|
||||||
complete -c vaptvupt -n "__fish_zupt_using_subcommand $rw_cmds" -s t -l threads -d 'Thread count' -x
|
|
||||||
complete -c zupt -n "__fish_zupt_using_subcommand $rw_cmds" -s t -l threads -d 'Thread count' -x
|
|
||||||
|
|
||||||
# ─── Disk subcommand ───
|
|
||||||
complete -c vaptvupt -f -n "__fish_zupt_using_subcommand disk; and not __fish_seen_subcommand_from backup restore" \
|
|
||||||
complete -c zupt -f -n "__fish_zupt_using_subcommand disk; and not __fish_seen_subcommand_from backup restore" \
|
|
||||||
-a 'backup' -d 'Read a block device into an archive'
|
|
||||||
complete -c vaptvupt -f -n "__fish_zupt_using_subcommand disk; and not __fish_seen_subcommand_from backup restore" \
|
|
||||||
complete -c zupt -f -n "__fish_zupt_using_subcommand disk; and not __fish_seen_subcommand_from backup restore" \
|
|
||||||
-a 'restore' -d 'Write an archive to a block device'
|
|
||||||
|
|
||||||
# ─── Keygen options ───
|
|
||||||
complete -c vaptvupt -n '__fish_zupt_using_subcommand keygen' -l sdk -d 'Generate SDK v2 keypair'
|
|
||||||
complete -c zupt -n '__fish_zupt_using_subcommand keygen' -l sdk -d 'Generate SDK v2 keypair'
|
|
||||||
complete -c vaptvupt -n '__fish_zupt_using_subcommand keygen' -l pq-sdk -d 'Same as --sdk'
|
|
||||||
complete -c zupt -n '__fish_zupt_using_subcommand keygen' -l pq-sdk -d 'Same as --sdk'
|
|
||||||
complete -c vaptvupt -n '__fish_zupt_using_subcommand keygen' -s o -d 'Output keyfile path' -r
|
|
||||||
complete -c zupt -n '__fish_zupt_using_subcommand keygen' -s o -d 'Output keyfile path' -r
|
|
||||||
complete -c vaptvupt -n '__fish_zupt_using_subcommand keygen' -l pub -d 'Export public key from -k'
|
|
||||||
complete -c zupt -n '__fish_zupt_using_subcommand keygen' -l pub -d 'Export public key from -k'
|
|
||||||
complete -c vaptvupt -n '__fish_zupt_using_subcommand keygen' -s k -d 'Source private keyfile' -r
|
|
||||||
complete -c zupt -n '__fish_zupt_using_subcommand keygen' -s k -d 'Source private keyfile' -r
|
|
||||||
112
completions/zupt.bash
Normal file
112
completions/zupt.bash
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
# bash completion for ZUPT
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
_zupt()
|
||||||
|
{
|
||||||
|
local cur prev command disk_command
|
||||||
|
COMPREPLY=()
|
||||||
|
cur=${COMP_WORDS[COMP_CWORD]}
|
||||||
|
prev=${COMP_WORDS[COMP_CWORD-1]}
|
||||||
|
command=${COMP_WORDS[1]-}
|
||||||
|
disk_command=${COMP_WORDS[2]-}
|
||||||
|
|
||||||
|
case $prev in
|
||||||
|
-l|--level)
|
||||||
|
COMPREPLY=( $(compgen -W '1 2 3 4 5 6 7 8 9' -- "$cur") )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
-b|--block|-t|--threads|--pass-fd|-p|--password|-c|--comment)
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--kdf)
|
||||||
|
COMPREPLY=( $(compgen -W 'pbkdf2 argon2id' -- "$cur") )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
-o|--output|-k|--key|--pass-file|--comment-file|--pq|--pq-only|--pq-sdk|--pq-box)
|
||||||
|
COMPREPLY=( $(compgen -f -- "$cur") )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if (( COMP_CWORD == 1 )); then
|
||||||
|
COMPREPLY=( $(compgen -W \
|
||||||
|
'compress c extract x list l test t info i bench b disk keygen version help --version -V --help -h' \
|
||||||
|
-- "$cur") )
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local password_options='-p --password --password-prompt --pass-file --pass-fd'
|
||||||
|
local pq_options='--pq --pq-only --pq-sdk --pq-box'
|
||||||
|
local legacy_read_option='--allow-legacy-no-ait'
|
||||||
|
local common_read_options="-v --verbose $password_options $pq_options $legacy_read_option"
|
||||||
|
|
||||||
|
case $command in
|
||||||
|
compress|c)
|
||||||
|
if [[ $cur == -* ]]; then
|
||||||
|
COMPREPLY=( $(compgen -W \
|
||||||
|
"-l --level -b --block -s --store -f --fast --vv --vaptvupt --lzhp
|
||||||
|
$password_options --kdf -c --comment --comment-file $pq_options
|
||||||
|
-D --dedup -S --solid -y --force -v --verbose -t --threads" \
|
||||||
|
-- "$cur") )
|
||||||
|
else
|
||||||
|
COMPREPLY=( $(compgen -f -- "$cur") )
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
extract|x)
|
||||||
|
if [[ $cur == -* ]]; then
|
||||||
|
COMPREPLY=( $(compgen -W \
|
||||||
|
"-o --output $common_read_options -t --threads" -- "$cur") )
|
||||||
|
else
|
||||||
|
COMPREPLY=( $(compgen -f -X '!*.zupt' -- "$cur") )
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
list|l|test|t)
|
||||||
|
if [[ $cur == -* ]]; then
|
||||||
|
COMPREPLY=( $(compgen -W "$common_read_options" -- "$cur") )
|
||||||
|
else
|
||||||
|
COMPREPLY=( $(compgen -f -X '!*.zupt' -- "$cur") )
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
info|i)
|
||||||
|
COMPREPLY=( $(compgen -f -X '!*.zupt' -- "$cur") )
|
||||||
|
;;
|
||||||
|
bench|b)
|
||||||
|
if [[ $cur == -* ]]; then
|
||||||
|
COMPREPLY=( $(compgen -W '--compare' -- "$cur") )
|
||||||
|
else
|
||||||
|
COMPREPLY=( $(compgen -f -- "$cur") )
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
disk)
|
||||||
|
if (( COMP_CWORD == 2 )); then
|
||||||
|
COMPREPLY=( $(compgen -W 'backup restore' -- "$cur") )
|
||||||
|
elif [[ $cur == -* ]]; then
|
||||||
|
local disk_options
|
||||||
|
disk_options="-l --level -b --block -s --store --vv --vaptvupt --lzhp
|
||||||
|
$password_options --kdf -c --comment --comment-file
|
||||||
|
--pq --pq-only -D --dedup -v --verbose -t --threads"
|
||||||
|
if [[ $disk_command == restore ]]; then
|
||||||
|
disk_options+=" $legacy_read_option"
|
||||||
|
fi
|
||||||
|
COMPREPLY=( $(compgen -W \
|
||||||
|
"$disk_options" \
|
||||||
|
-- "$cur") )
|
||||||
|
elif [[ $disk_command == restore ]]; then
|
||||||
|
COMPREPLY=( $(compgen -f -X '!*.zupt' -- "$cur") )
|
||||||
|
else
|
||||||
|
COMPREPLY=( $(compgen -f -- "$cur") )
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
keygen)
|
||||||
|
if [[ $cur == -* ]]; then
|
||||||
|
COMPREPLY=( $(compgen -W \
|
||||||
|
'-o --output --pub -k --key --pq-only --pqonly --sdk --pq-sdk --box --pq-box' \
|
||||||
|
-- "$cur") )
|
||||||
|
else
|
||||||
|
COMPREPLY=( $(compgen -f -- "$cur") )
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _zupt zupt
|
||||||
163
completions/zupt.fish
Normal file
163
completions/zupt.fish
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
# fish completion for ZUPT
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
function __fish_zupt_needs_command
|
||||||
|
set -l tokens (commandline -opc)
|
||||||
|
test (count $tokens) -eq 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function __fish_zupt_using_command
|
||||||
|
set -l tokens (commandline -opc)
|
||||||
|
test (count $tokens) -gt 1; and contains -- $tokens[2] $argv
|
||||||
|
end
|
||||||
|
|
||||||
|
function __fish_zupt_disk_needs_command
|
||||||
|
set -l tokens (commandline -opc)
|
||||||
|
test (count $tokens) -eq 2; and test "$tokens[2]" = disk
|
||||||
|
end
|
||||||
|
|
||||||
|
function __fish_zupt_disk_using_command
|
||||||
|
set -l tokens (commandline -opc)
|
||||||
|
test (count $tokens) -gt 2; and test "$tokens[2]" = disk; and contains -- $tokens[3] $argv
|
||||||
|
end
|
||||||
|
|
||||||
|
complete -c zupt -f -n __fish_zupt_needs_command \
|
||||||
|
-a 'compress c' -d 'Create an archive'
|
||||||
|
complete -c zupt -f -n __fish_zupt_needs_command \
|
||||||
|
-a 'extract x' -d 'Extract an archive'
|
||||||
|
complete -c zupt -f -n __fish_zupt_needs_command \
|
||||||
|
-a 'list l' -d 'List archive entries'
|
||||||
|
complete -c zupt -f -n __fish_zupt_needs_command \
|
||||||
|
-a 'test t' -d 'Verify archive integrity'
|
||||||
|
complete -c zupt -f -n __fish_zupt_needs_command \
|
||||||
|
-a 'info i' -d 'Show archive framing metadata'
|
||||||
|
complete -c zupt -f -n __fish_zupt_needs_command \
|
||||||
|
-a 'bench b' -d 'Benchmark compression levels'
|
||||||
|
complete -c zupt -f -n __fish_zupt_needs_command \
|
||||||
|
-a disk -d 'Back up or restore a disk image'
|
||||||
|
complete -c zupt -f -n __fish_zupt_needs_command \
|
||||||
|
-a keygen -d 'Generate or export a recipient key'
|
||||||
|
complete -c zupt -f -n __fish_zupt_needs_command \
|
||||||
|
-a version -d 'Show version and build information'
|
||||||
|
complete -c zupt -f -n __fish_zupt_needs_command \
|
||||||
|
-a help -d 'Show command help'
|
||||||
|
complete -c zupt -f -n __fish_zupt_needs_command \
|
||||||
|
-a '--version -V' -d 'Show version and build information'
|
||||||
|
complete -c zupt -f -n __fish_zupt_needs_command \
|
||||||
|
-a '--help -h' -d 'Show command help'
|
||||||
|
|
||||||
|
set -l compress_condition '__fish_zupt_using_command compress c'
|
||||||
|
complete -c zupt -n "$compress_condition" -s l -l level \
|
||||||
|
-d 'Compression level' -x -a '1 2 3 4 5 6 7 8 9'
|
||||||
|
complete -c zupt -n "$compress_condition" -s b -l block \
|
||||||
|
-d 'Block size in bytes' -x
|
||||||
|
complete -c zupt -n "$compress_condition" -s s -l store \
|
||||||
|
-d 'Store without compression'
|
||||||
|
complete -c zupt -n "$compress_condition" -s f -l fast \
|
||||||
|
-d 'Use fast LZ codec'
|
||||||
|
complete -c zupt -n "$compress_condition" -l vv -l vaptvupt \
|
||||||
|
-d 'Force VaptVupt LZ + ANS codec'
|
||||||
|
complete -c zupt -n "$compress_condition" -l lzhp \
|
||||||
|
-d 'Force portable LZHP codec'
|
||||||
|
complete -c zupt -n "$compress_condition" -l kdf \
|
||||||
|
-d 'Password KDF' -x -a 'pbkdf2 argon2id'
|
||||||
|
complete -c zupt -n "$compress_condition" -s c -l comment \
|
||||||
|
-d 'Store an archive comment' -x
|
||||||
|
complete -c zupt -n "$compress_condition" -l comment-file \
|
||||||
|
-d 'Read archive comment from file' -r
|
||||||
|
complete -c zupt -n "$compress_condition" -s D -l dedup \
|
||||||
|
-d 'Enable block-level deduplication'
|
||||||
|
complete -c zupt -n "$compress_condition" -s S -l solid \
|
||||||
|
-d 'Use a solid single compression stream'
|
||||||
|
complete -c zupt -n "$compress_condition" -s y -l force \
|
||||||
|
-d 'Overwrite an existing non-.zupt output'
|
||||||
|
complete -c zupt -n "$compress_condition" -s t -l threads \
|
||||||
|
-d 'Compression thread count' -x
|
||||||
|
|
||||||
|
set -l read_condition '__fish_zupt_using_command compress c extract x list l test t'
|
||||||
|
complete -c zupt -n "$read_condition" -s p -l password \
|
||||||
|
-d 'Password in process arguments' -x
|
||||||
|
complete -c zupt -n "$read_condition" -l password-prompt \
|
||||||
|
-d 'Read password interactively without echo'
|
||||||
|
complete -c zupt -n "$read_condition" -l pass-file \
|
||||||
|
-d 'Read password from first line of file' -r
|
||||||
|
complete -c zupt -n "$read_condition" -l pass-fd \
|
||||||
|
-d 'Read password from inherited file descriptor' -x
|
||||||
|
complete -c zupt -n "$read_condition" -l pq \
|
||||||
|
-d 'Native ML-KEM-768 + X25519 hybrid key' -r
|
||||||
|
complete -c zupt -n "$read_condition" -l pq-only \
|
||||||
|
-d 'Native ML-KEM-768-only key' -r
|
||||||
|
complete -c zupt -n "$read_condition" -l pq-sdk \
|
||||||
|
-d 'Optional system libvuptsdk key' -r
|
||||||
|
complete -c zupt -n "$read_condition" -l pq-box \
|
||||||
|
-d 'Optional system libpqvaptvupt key' -r
|
||||||
|
complete -c zupt -n "$read_condition" -s v -l verbose \
|
||||||
|
-d 'Additional progress or diagnostic output'
|
||||||
|
|
||||||
|
set -l legacy_read_condition '__fish_zupt_using_command extract x list l test t'
|
||||||
|
complete -c zupt -n "$legacy_read_condition" -l allow-legacy-no-ait \
|
||||||
|
-d 'Recover a trusted old archive without an integrity trailer'
|
||||||
|
|
||||||
|
complete -c zupt -n '__fish_zupt_using_command extract x' \
|
||||||
|
-s o -l output -d 'Output directory' -r
|
||||||
|
complete -c zupt -n '__fish_zupt_using_command extract x' \
|
||||||
|
-s t -l threads -d 'Decompression thread count' -x
|
||||||
|
complete -c zupt -n '__fish_zupt_using_command bench b' \
|
||||||
|
-l compare -d 'Compare available external compressors'
|
||||||
|
|
||||||
|
complete -c zupt -f -n __fish_zupt_disk_needs_command \
|
||||||
|
-a backup -d 'Create a disk-image archive'
|
||||||
|
complete -c zupt -f -n __fish_zupt_disk_needs_command \
|
||||||
|
-a restore -d 'Restore a disk-image archive'
|
||||||
|
set -l disk_condition '__fish_zupt_using_command disk'
|
||||||
|
complete -c zupt -n "$disk_condition" -s l -l level \
|
||||||
|
-d 'Compression level' -x -a '1 2 3 4 5 6 7 8 9'
|
||||||
|
complete -c zupt -n "$disk_condition" -s b -l block \
|
||||||
|
-d 'Block size in bytes' -x
|
||||||
|
complete -c zupt -n "$disk_condition" -s s -l store \
|
||||||
|
-d 'Store without compression'
|
||||||
|
complete -c zupt -n "$disk_condition" -l vv -l vaptvupt \
|
||||||
|
-d 'Force VaptVupt LZ + ANS codec'
|
||||||
|
complete -c zupt -n "$disk_condition" -l lzhp \
|
||||||
|
-d 'Force portable LZHP codec'
|
||||||
|
complete -c zupt -n "$disk_condition" -s p -l password \
|
||||||
|
-d 'Password in process arguments' -x
|
||||||
|
complete -c zupt -n "$disk_condition" -l password-prompt \
|
||||||
|
-d 'Read password interactively without echo'
|
||||||
|
complete -c zupt -n "$disk_condition" -l pass-file \
|
||||||
|
-d 'Read password from first line of file' -r
|
||||||
|
complete -c zupt -n "$disk_condition" -l pass-fd \
|
||||||
|
-d 'Read password from inherited file descriptor' -x
|
||||||
|
complete -c zupt -n "$disk_condition" -l kdf \
|
||||||
|
-d 'Password KDF' -x -a 'pbkdf2 argon2id'
|
||||||
|
complete -c zupt -n "$disk_condition" -s c -l comment \
|
||||||
|
-d 'Store an archive comment' -x
|
||||||
|
complete -c zupt -n "$disk_condition" -l comment-file \
|
||||||
|
-d 'Read archive comment from file' -r
|
||||||
|
complete -c zupt -n "$disk_condition" -l pq \
|
||||||
|
-d 'Native ML-KEM-768 + X25519 hybrid key' -r
|
||||||
|
complete -c zupt -n "$disk_condition" -l pq-only \
|
||||||
|
-d 'Native ML-KEM-768-only key' -r
|
||||||
|
complete -c zupt -n "$disk_condition" -s D -l dedup \
|
||||||
|
-d 'Enable block-level deduplication'
|
||||||
|
complete -c zupt -n "$disk_condition" -s v -l verbose \
|
||||||
|
-d 'Additional progress or diagnostic output'
|
||||||
|
complete -c zupt -n "$disk_condition" -s t -l threads \
|
||||||
|
-d 'Thread count' -x
|
||||||
|
complete -c zupt -n '__fish_zupt_disk_using_command restore' \
|
||||||
|
-l allow-legacy-no-ait \
|
||||||
|
-d 'Recover a trusted old disk archive without an integrity trailer'
|
||||||
|
|
||||||
|
set -l keygen_condition '__fish_zupt_using_command keygen'
|
||||||
|
complete -c zupt -n "$keygen_condition" -s o -l output \
|
||||||
|
-d 'Output key file' -r
|
||||||
|
complete -c zupt -n "$keygen_condition" -l pub \
|
||||||
|
-d 'Export a public key'
|
||||||
|
complete -c zupt -n "$keygen_condition" -s k -l key \
|
||||||
|
-d 'Source private key' -r
|
||||||
|
complete -c zupt -n "$keygen_condition" -l pq-only -l pqonly \
|
||||||
|
-d 'Native ML-KEM-768-only key format'
|
||||||
|
complete -c zupt -n "$keygen_condition" -l sdk -l pq-sdk \
|
||||||
|
-d 'Optional system libvuptsdk key format'
|
||||||
|
complete -c zupt -n "$keygen_condition" -l box -l pq-box \
|
||||||
|
-d 'Optional system libpqvaptvupt key format'
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
.\" Manpage for vaptvupt-gui (formerly zupt-gui; INPI Brasil trademark rename in v3.0.0)
|
|
||||||
.\" SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
.\" Copyright (c) 2025-2026 Cristian Cezar Moisés
|
|
||||||
.TH VAPTVUPT-GUI 1 "2026-06-11" "vaptvupt-gui 1.3.0" "User Commands"
|
|
||||||
.SH NAME
|
|
||||||
vaptvupt-gui \- graphical interface for the VaptVupt post-quantum backup utility
|
|
||||||
.SH SYNOPSIS
|
|
||||||
.B vaptvupt-gui
|
|
||||||
.RI [ ARCHIVE ]
|
|
||||||
.SH DESCRIPTION
|
|
||||||
.B vaptvupt-gui
|
|
||||||
is a graphical frontend for
|
|
||||||
.BR vaptvupt (1).
|
|
||||||
It provides tabs for compression, extraction, key management, and
|
|
||||||
full-disk backup. Both PQ encryption modes are exposed:
|
|
||||||
.B legacy --pq
|
|
||||||
and
|
|
||||||
.B SDK v2 --pq-sdk
|
|
||||||
(HKDF combiner, key commitment, HPKE binding, Argon2id).
|
|
||||||
|
|
||||||
The legacy command name
|
|
||||||
.B zupt-gui
|
|
||||||
is preserved as a symlink for backward compatibility; both invocations
|
|
||||||
behave identically.
|
|
||||||
|
|
||||||
If
|
|
||||||
.I ARCHIVE
|
|
||||||
is given on the command line, the GUI opens directly on the
|
|
||||||
extract tab with that archive preloaded.
|
|
||||||
|
|
||||||
.B vaptvupt-gui
|
|
||||||
uses Qt 6. It works with either of the following Python Qt bindings,
|
|
||||||
auto-detected at startup in this order:
|
|
||||||
.IP \(bu 2
|
|
||||||
PySide6 (Qt for Python)
|
|
||||||
.IP \(bu 2
|
|
||||||
PyQt6
|
|
||||||
.PP
|
|
||||||
If neither is installed, the GUI prints an instructive error and exits.
|
|
||||||
|
|
||||||
.SH TABS
|
|
||||||
.TP
|
|
||||||
.B Compress
|
|
||||||
Select files or directories, choose codec, level, password and/or PQ
|
|
||||||
key. The
|
|
||||||
.B Mode
|
|
||||||
panel controls whether the SDK v2 path or the legacy path is used.
|
|
||||||
.TP
|
|
||||||
.B Extract
|
|
||||||
Open a .zupt archive, select output directory, provide password
|
|
||||||
and/or PQ private key.
|
|
||||||
.TP
|
|
||||||
.B Keygen
|
|
||||||
Generate ML-KEM-768 + X25519 keypair. The
|
|
||||||
.B SDK v2 format
|
|
||||||
checkbox controls whether the keypair is generated via
|
|
||||||
.B vaptvupt keygen --sdk
|
|
||||||
(producing
|
|
||||||
.IR file
|
|
||||||
and
|
|
||||||
.IR file.pub
|
|
||||||
in one step) or via the legacy
|
|
||||||
.BR "vaptvupt keygen" .
|
|
||||||
.TP
|
|
||||||
.B Disk
|
|
||||||
Full-disk backup and restore. Enumerates block devices with
|
|
||||||
human-readable sizes. Same encryption mode controls as Compress.
|
|
||||||
|
|
||||||
.SH FILES
|
|
||||||
.TP
|
|
||||||
.I /usr/bin/vaptvupt-gui
|
|
||||||
Wrapper script that invokes the Python entry point (and the symlinked
|
|
||||||
legacy
|
|
||||||
.IR /usr/bin/zupt-gui ).
|
|
||||||
.TP
|
|
||||||
.I /usr/lib/vaptvupt-gui/zupt_gui.py
|
|
||||||
Main Python source.
|
|
||||||
.TP
|
|
||||||
.I /usr/share/applications/vaptvupt-gui.desktop
|
|
||||||
Desktop entry for menu integration.
|
|
||||||
.TP
|
|
||||||
.I /usr/share/icons/hicolor/256x256/apps/vaptvupt-gui.png
|
|
||||||
Application icon.
|
|
||||||
|
|
||||||
.SH ENVIRONMENT
|
|
||||||
.TP
|
|
||||||
.B VAPTVUPT_BIN
|
|
||||||
Override the path to the
|
|
||||||
.B vaptvupt
|
|
||||||
binary (default: search
|
|
||||||
.IR PATH ).
|
|
||||||
The legacy name
|
|
||||||
.B ZUPT_BIN
|
|
||||||
is also honoured.
|
|
||||||
.TP
|
|
||||||
.B VAPTVUPT_DEBUG
|
|
||||||
Enable binary-discovery debug logging on stderr. The legacy name
|
|
||||||
.B ZUPT_DEBUG
|
|
||||||
is also honoured.
|
|
||||||
|
|
||||||
.SH BUGS
|
|
||||||
Report at
|
|
||||||
.UR https://git.securityops.co/cristiancmoises/vaptvupt/issues
|
|
||||||
.UE .
|
|
||||||
|
|
||||||
.SH AUTHOR
|
|
||||||
Cristian Cezar Moisés
|
|
||||||
.MT zupt@riseup.net
|
|
||||||
.ME
|
|
||||||
|
|
||||||
.SH SEE ALSO
|
|
||||||
.BR vaptvupt (1).
|
|
||||||
|
|
||||||
.SH LICENSE
|
|
||||||
.PP
|
|
||||||
vaptvupt-gui is licensed under the
|
|
||||||
.B GNU Affero General Public License version 3 or later
|
|
||||||
(AGPL-3.0-or-later). Commercial license available for relief from
|
|
||||||
copyleft terms; contact
|
|
||||||
.MT sac@securityops.co
|
|
||||||
.ME .
|
|
||||||
|
|
||||||
.SH PROJECT
|
|
||||||
.PP
|
|
||||||
Home page:
|
|
||||||
.UR https://git.securityops.co/cristiancmoises/vaptvupt
|
|
||||||
.UE
|
|
||||||
730
doc/vaptvupt.1
730
doc/vaptvupt.1
|
|
@ -1,730 +0,0 @@
|
||||||
.\" Manpage for vaptvupt (formerly zupt; INPI Brasil trademark rename in v3.0.0)
|
|
||||||
.\" SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
.\" Copyright (c) 2025-2026 Cristian Cezar Moisés
|
|
||||||
.TH VAPTVUPT 1 "July 2026" "vaptvupt 5.0.0" "User Commands"
|
|
||||||
|
|
||||||
.SH NAME
|
|
||||||
vaptvupt \- post-quantum backup compression utility (formerly zupt)
|
|
||||||
|
|
||||||
.SH SYNOPSIS
|
|
||||||
.B vaptvupt compress
|
|
||||||
.RI [ options ]
|
|
||||||
.I out.zupt
|
|
||||||
.I files...
|
|
||||||
.br
|
|
||||||
.B vaptvupt extract
|
|
||||||
.RI [ options ]
|
|
||||||
.I archive.zupt
|
|
||||||
.br
|
|
||||||
.B vaptvupt list
|
|
||||||
.RI [ options ]
|
|
||||||
.I archive.zupt
|
|
||||||
.br
|
|
||||||
.B vaptvupt test
|
|
||||||
.RI [ options ]
|
|
||||||
.I archive.zupt
|
|
||||||
.br
|
|
||||||
.B vaptvupt info
|
|
||||||
.I archive.zupt
|
|
||||||
.br
|
|
||||||
.B vaptvupt bench
|
|
||||||
.I files/dirs...
|
|
||||||
.br
|
|
||||||
.B vaptvupt disk backup
|
|
||||||
.RI [ options ]
|
|
||||||
.I out.zupt
|
|
||||||
.I device
|
|
||||||
.br
|
|
||||||
.B vaptvupt disk restore
|
|
||||||
.RI [ options ]
|
|
||||||
.I archive.zupt
|
|
||||||
.I device
|
|
||||||
.br
|
|
||||||
.B vaptvupt keygen
|
|
||||||
.RI [ options ]
|
|
||||||
.br
|
|
||||||
.B vaptvupt version
|
|
||||||
.br
|
|
||||||
.B vaptvupt help
|
|
||||||
|
|
||||||
.PP
|
|
||||||
The legacy command name
|
|
||||||
.B zupt
|
|
||||||
is preserved as an alias for backward compatibility; both invocations
|
|
||||||
behave identically.
|
|
||||||
|
|
||||||
.SH DESCRIPTION
|
|
||||||
.B vaptvupt
|
|
||||||
compresses and encrypts files, directories, and whole block devices
|
|
||||||
into self-contained, authenticated archives with the
|
|
||||||
.B .zupt
|
|
||||||
extension. It targets long-lived backup storage where:
|
|
||||||
|
|
||||||
.RS
|
|
||||||
.IP \(bu 2
|
|
||||||
the archive is written once and restored under time pressure many years later;
|
|
||||||
.IP \(bu 2
|
|
||||||
the encryption envelope must remain secure against a future cryptographically-relevant quantum computer (ML-KEM-768);
|
|
||||||
.IP \(bu 2
|
|
||||||
every byte of the archive — header, footer, per-block metadata, comments — is authenticated, and a single bit-flip is rejected at restore time.
|
|
||||||
.RE
|
|
||||||
|
|
||||||
.PP
|
|
||||||
The compression layer is the
|
|
||||||
.B VaptVupt LZ + ANS
|
|
||||||
codec (version 2.60.4), which prioritises decode speed and ratio over
|
|
||||||
encode speed. Aggregate decode throughput on this build is 1.27\(mu
|
|
||||||
zstd\-3; encode throughput is 0.2\(mu\(en0.5\(mu zstd\-3 depending on
|
|
||||||
content. See
|
|
||||||
.B PERFORMANCE
|
|
||||||
below.
|
|
||||||
|
|
||||||
.PP
|
|
||||||
The on-disk format is v1.6 and has been wire-compatible since release
|
|
||||||
v2.3.1. The product was renamed from
|
|
||||||
.B Zupt
|
|
||||||
to
|
|
||||||
.B VaptVupt
|
|
||||||
in v3.0.0 because of a prior INPI Brasil trademark registration of the
|
|
||||||
name "Zupt" for unrelated software. The
|
|
||||||
.B .zupt
|
|
||||||
file extension and the
|
|
||||||
.B ZUPT
|
|
||||||
header magic bytes are unchanged: archives produced by any v2.x release
|
|
||||||
extract cleanly under v3.0.0 and vice versa.
|
|
||||||
|
|
||||||
.SH COMMANDS
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B compress
|
|
||||||
Create an archive. Default codec is VaptVupt (level 7). Compression
|
|
||||||
is multi-threaded; one worker per detected CPU by default.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B extract
|
|
||||||
Decompress an archive into the current directory (or
|
|
||||||
.BR -o " " \fIdir\fR ).
|
|
||||||
Refuses to write outside the destination directory (path-traversal
|
|
||||||
defence). Files are written with their original permissions and
|
|
||||||
mtime preserved.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B list
|
|
||||||
Print archive metadata: per-file path, size, mtime, mode, compressed
|
|
||||||
size, codec. With
|
|
||||||
.B --verbose
|
|
||||||
also prints per-block sizes and HMAC tags (first 8 bytes).
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B test
|
|
||||||
Decompress all blocks in memory and verify HMAC tags + archive
|
|
||||||
integrity trailer. Does not write any files. Use to validate an
|
|
||||||
archive without restoring it. Exit code is non-zero on any failure.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B info
|
|
||||||
Print archive header metadata without requiring the decryption key.
|
|
||||||
Reports: format version, codec, encryption type (none / PBKDF2 /
|
|
||||||
Argon2id / ML-KEM-768+X25519 hybrid / ML-KEM-768 pure-PQ), KDF
|
|
||||||
iteration count, file count,
|
|
||||||
creation timestamp, archive UUID, AIT presence. Safe to run on an
|
|
||||||
untrusted archive.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B bench
|
|
||||||
Compare compression levels 1\(en9 on the supplied files; reports
|
|
||||||
ratio and encode/decode throughput per level. Useful when picking
|
|
||||||
the right
|
|
||||||
.B -l
|
|
||||||
for a given workload.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B disk backup
|
|
||||||
Read a block device and write a sparse-aware archive. Detects
|
|
||||||
all-zero regions and records them as runs rather than compressing
|
|
||||||
them.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B disk restore
|
|
||||||
Inverse of
|
|
||||||
.BR "disk backup" .
|
|
||||||
Writes the archive's contents back to a block device. Verifies
|
|
||||||
target device size before writing; refuses if the target is smaller
|
|
||||||
than the archived size. With
|
|
||||||
.B --sync
|
|
||||||
issues
|
|
||||||
.BR fsync (2)
|
|
||||||
after each block.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B keygen
|
|
||||||
Generate a key file. With no PQ flag, writes a 32-byte raw key for
|
|
||||||
keyfile-mode encryption. With
|
|
||||||
.B --pq
|
|
||||||
generates a native ML-KEM-768 + X25519 \fBhybrid\fR keypair for
|
|
||||||
.B --pq
|
|
||||||
encryption (in-tree crypto; no external library). With
|
|
||||||
.B --pq-only
|
|
||||||
generates a native pure ML-KEM-768 keypair (magic
|
|
||||||
.BR ZPQK )
|
|
||||||
for
|
|
||||||
.B --pq-only
|
|
||||||
encryption. With
|
|
||||||
.B --sdk
|
|
||||||
generates a keypair for the optional
|
|
||||||
.B --pq-sdk
|
|
||||||
mode, and with
|
|
||||||
.B --box
|
|
||||||
a libpqvaptvupt sealed-box keypair for
|
|
||||||
.B --pq-box
|
|
||||||
(both need a
|
|
||||||
.B WITH_SDK=1
|
|
||||||
build). With
|
|
||||||
.B --pub
|
|
||||||
extracts the public key from an existing private key (combine with the
|
|
||||||
matching PQ flag, e.g.
|
|
||||||
.BR "keygen --pub --pq-only" ).
|
|
||||||
|
|
||||||
.SH GLOBAL OPTIONS
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.BR -v ", " --verbose
|
|
||||||
Print per-file and per-block details during compress/extract/list/test.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.BR -q ", " --quiet
|
|
||||||
Suppress non-error output.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.BR -j " " \fIN\fR ", " --jobs " " \fIN\fR
|
|
||||||
Worker thread count for parallel compression. Default: number of
|
|
||||||
online CPUs.
|
|
||||||
|
|
||||||
.SH COMPRESS OPTIONS
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.BR -l " " \fI1..9\fR ", " --level " " \fI1..9\fR
|
|
||||||
Compression level. 1\(en2 = ultra-fast (~80 MB/s encode on typical
|
|
||||||
hardware, lower ratio). 3\(en7 = balanced (default 7). 8\(en9 =
|
|
||||||
extreme (optimal parsing, ~5\(en10\(mu slower encode, best ratio).
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B --codec \fIid\fR
|
|
||||||
Force a specific codec by id. Accepted values:
|
|
||||||
.BR store " (0x0000), "
|
|
||||||
.BR vaptvupt-lz " (0x0008), "
|
|
||||||
.BR vaptvupt-lzh " (0x0009), "
|
|
||||||
.BR vaptvupt-lzhp " (0x000A), "
|
|
||||||
.BR vaptvupt " (0x0010 — default), "
|
|
||||||
.BR auto " (0xFFFF — pick at runtime)."
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.BR -p " " \fIpassword\fR
|
|
||||||
Enable password-based encryption (PBKDF2-SHA256 KDF; Argon2id is available
|
|
||||||
only in a WITH_SDK=1 build via \fB--kdf argon2id\fR).
|
|
||||||
Reading the password from a flag exposes it in
|
|
||||||
.BR ps (1)
|
|
||||||
output; prefer
|
|
||||||
.B --pass-file
|
|
||||||
or interactive prompt.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B --pass-file \fIpath\fR
|
|
||||||
Read password from the first line of the file. The file's permission
|
|
||||||
bits should be 0600.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B --pass-fd \fIN\fR
|
|
||||||
Read password from file descriptor N.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B --kdf \fIalgo\fR
|
|
||||||
Choose key-derivation function for password mode. In the default source-only
|
|
||||||
build the only KDF is
|
|
||||||
.BR pbkdf2 " (SHA-256, 600 000 iter), which is the default;"
|
|
||||||
.BR argon2id " (memory-hard) is available only in a " WITH_SDK=1 " build."
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B --keyfile \fIpath\fR
|
|
||||||
Use a 32-byte raw key file (generated with
|
|
||||||
.BR "vaptvupt keygen" ).
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B --pq \fIpub\fR
|
|
||||||
Enable native post-quantum \fBhybrid\fR encryption (envelope type 0x02,
|
|
||||||
recommended). Combines ML-KEM-768 (FIPS 203) with X25519 (RFC 7748) so
|
|
||||||
the archive key is secure unless \fBboth\fR the lattice KEM and the
|
|
||||||
elliptic-curve exchange are broken. Uses the in-tree crypto only — no
|
|
||||||
external library, always available. The
|
|
||||||
.I pub
|
|
||||||
argument is the recipient's public-key file from
|
|
||||||
.BR "vaptvupt keygen" .
|
|
||||||
On extraction, pass the secret key:
|
|
||||||
.B --pq
|
|
||||||
\fIpriv\fR.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B --pq-only \fIpub\fR
|
|
||||||
Enable native \fBfull\fR (pure) post-quantum encryption (envelope type
|
|
||||||
0x06). ML-KEM-768 is the \fIsole\fR key-establishment mechanism — no
|
|
||||||
X25519 component. Choose this only when a policy mandates a single
|
|
||||||
NIST-standardised PQ primitive with no classical KEM in the envelope
|
|
||||||
(e.g. CNSA 2.0-style "PQ-only" postures). The archive key is
|
|
||||||
SHA3-512(ml_ss || ml_ct || "ZUPT-PQ-ONLY-v1"). Note the deliberate
|
|
||||||
trade-off: unlike
|
|
||||||
.BR --pq ,
|
|
||||||
a future weakness in ML-KEM-768 alone is sufficient to break the
|
|
||||||
envelope, because there is no classical KEM to fall back on. When in
|
|
||||||
doubt use
|
|
||||||
.B --pq
|
|
||||||
(hybrid). Keys are generated with
|
|
||||||
.BR "vaptvupt keygen --pq-only" ;
|
|
||||||
the private and public key files (magic
|
|
||||||
.BR ZPQK )
|
|
||||||
are not interchangeable with hybrid
|
|
||||||
.B --pq
|
|
||||||
keys. On extraction, pass the secret key:
|
|
||||||
.B --pq-only
|
|
||||||
\fIpriv\fR.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B --pq-box \fIpub\fR
|
|
||||||
Enable post-quantum sealed-box encryption via libpqvaptvupt (envelope
|
|
||||||
type 0x05). \fBRequires an optional\fR \fBWITH_SDK=1\fR \fBbuild\fR: the
|
|
||||||
default source-only tree ships no vendored library, so this mode is
|
|
||||||
absent unless you build against libpqvaptvupt yourself. Prefer the
|
|
||||||
native
|
|
||||||
.B --pq
|
|
||||||
or
|
|
||||||
.B --pq-only
|
|
||||||
modes, which need no external library. ML-KEM-768 + X25519 shared secrets are
|
|
||||||
combined through HKDF-SHA256 with a domain-separating info string
|
|
||||||
("pqvv-seal-v1"); the box carries AES-256-CTR + HMAC-SHA256
|
|
||||||
Encrypt-then-MAC. The
|
|
||||||
.I pub
|
|
||||||
argument is the recipient's public-key file from
|
|
||||||
.B keygen --box
|
|
||||||
(magic-tagged; public and secret key files are not interchangeable).
|
|
||||||
On extraction, pass the secret key:
|
|
||||||
.B --pq-box
|
|
||||||
\fIpriv\fR.
|
|
||||||
.TP
|
|
||||||
.B --pq-sdk \fIpub\fR
|
|
||||||
Enable post-quantum hybrid encryption via libzuptsdk (envelope type
|
|
||||||
0x03). \fBRequires an optional\fR \fBWITH_SDK=1\fR \fBbuild\fR and is
|
|
||||||
absent from the default source-only tree; use the native
|
|
||||||
.B --pq
|
|
||||||
instead, which provides the same ML-KEM-768 + X25519 hybrid with no
|
|
||||||
external dependency. Uses ML-KEM-768 + X25519 with HKDF combiner, HPKE
|
|
||||||
binding, and key commitment. The
|
|
||||||
.I pub
|
|
||||||
argument is the recipient's public-key file generated by
|
|
||||||
.BR "vaptvupt keygen --sdk" .
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B -c \fItext\fR ", " --comment " " \fItext\fR
|
|
||||||
Embed an encrypted UTF-8 comment in the archive (up to 4096 bytes).
|
|
||||||
The comment is bound to the archive's frame-preface AAD; tampering
|
|
||||||
is detected at extract time.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B --comment-file \fIpath\fR
|
|
||||||
Read the comment from a file rather than the command line.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B -b \fIsize\fR ", " --block-size " " \fIsize\fR
|
|
||||||
Compression block size. Default 4 MiB. Smaller blocks improve
|
|
||||||
random-access decode but lose some ratio.
|
|
||||||
|
|
||||||
.SH EXTRACT, LIST, TEST OPTIONS
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.BR -o " " \fIdir\fR ", " --output " " \fIdir\fR
|
|
||||||
Extract into
|
|
||||||
.IR dir
|
|
||||||
(created if it doesn't exist). Default: current directory.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B --no-mtime
|
|
||||||
Do not restore archived modification times; use current time instead.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B --strip-components \fIN\fR
|
|
||||||
Strip
|
|
||||||
.I N
|
|
||||||
leading path components from each entry, like
|
|
||||||
.BR tar 's
|
|
||||||
flag of the same name.
|
|
||||||
|
|
||||||
.SH POST-QUANTUM ENCRYPTION
|
|
||||||
|
|
||||||
.B vaptvupt
|
|
||||||
offers two native post-quantum modes, both built entirely from the
|
|
||||||
in-tree crypto (no external library):
|
|
||||||
|
|
||||||
.RS
|
|
||||||
.IP "\fB--pq\fR (hybrid, recommended)" 4
|
|
||||||
A hybrid KEM combining ML-KEM-768 (FIPS 203) with X25519 (RFC 7748).
|
|
||||||
The archive key is derived as:
|
|
||||||
.RS
|
|
||||||
.nf
|
|
||||||
|
|
||||||
ss_pq = ML-KEM-768.decaps(sk_pq, ct_pq)
|
|
||||||
ss_ec = X25519(sk_ec, pk_ec_peer)
|
|
||||||
session = HKDF-SHA256(ss_pq || ss_ec,
|
|
||||||
info = "vaptvupt-pq-sdk-v1",
|
|
||||||
salt = archive_uuid)
|
|
||||||
.fi
|
|
||||||
.RE
|
|
||||||
The hybrid combiner means the session key is at least as strong as the
|
|
||||||
strongest of {ML-KEM-768, X25519}: an attacker must break \fBboth\fR to
|
|
||||||
recover the key. This is the default recommendation and the right choice
|
|
||||||
for almost every user, because it stays secure even if one primitive is
|
|
||||||
later found weak.
|
|
||||||
.IP "\fB--pq-only\fR (full / pure PQ)" 4
|
|
||||||
ML-KEM-768 as the \fIsole\fR key-establishment mechanism, with no
|
|
||||||
classical component. The archive key is derived as:
|
|
||||||
.RS
|
|
||||||
.nf
|
|
||||||
|
|
||||||
(ss_pq, ct_pq) = ML-KEM-768.encaps(pk_pq)
|
|
||||||
archive_key = SHA3-512(ss_pq || ct_pq || "ZUPT-PQ-ONLY-v1")
|
|
||||||
.fi
|
|
||||||
.RE
|
|
||||||
Use this only when a compliance posture requires a single
|
|
||||||
NIST-standardised PQ primitive with no classical KEM in the envelope
|
|
||||||
(for example CNSA 2.0-style "PQ-only" requirements). The deliberate
|
|
||||||
trade-off is that the envelope has \fBno hybrid safety net\fR: a future
|
|
||||||
cryptanalytic break of ML-KEM-768 alone breaks the archive, whereas
|
|
||||||
under
|
|
||||||
.B --pq
|
|
||||||
the attacker would still have to break X25519 as well. Unless a policy
|
|
||||||
forbids the classical component, prefer
|
|
||||||
.BR --pq .
|
|
||||||
.RE
|
|
||||||
|
|
||||||
.PP
|
|
||||||
Both modes carry the same authenticated envelope as password mode:
|
|
||||||
per-block AES-256-CTR with a fresh random 128-bit nonce, HMAC-SHA256
|
|
||||||
Encrypt-then-MAC, and ML-KEM Fujisaki-Okamoto implicit rejection, so a
|
|
||||||
wrong or tampered ciphertext is rejected rather than yielding garbage.
|
|
||||||
|
|
||||||
.PP
|
|
||||||
.B Key commitment:
|
|
||||||
the ciphertext is bound to the exact public key it was encrypted to
|
|
||||||
via an HPKE-style derivation. An attacker cannot present a different
|
|
||||||
public key that decrypts to the same plaintext (this defeats the
|
|
||||||
"partitioning" attack class).
|
|
||||||
|
|
||||||
.PP
|
|
||||||
.B Implementation notes:
|
|
||||||
the ML-KEM-768 implementation is vendored from a clean reference and
|
|
||||||
verified against the FIPS 203 KAT vectors. The X25519 implementation
|
|
||||||
uses 4\(mu64-bit field arithmetic with Jasmin-verified constant-time
|
|
||||||
field operations on x86_64. On other architectures the same routines
|
|
||||||
run in pure C, also constant-time by construction.
|
|
||||||
|
|
||||||
.SH SECURITY
|
|
||||||
|
|
||||||
.SS Threat model
|
|
||||||
|
|
||||||
What
|
|
||||||
.B vaptvupt
|
|
||||||
.B protects against:
|
|
||||||
|
|
||||||
.RS
|
|
||||||
.IP \(bu 2
|
|
||||||
Confidentiality of archived data at rest (AES-256-CTR with HMAC-SHA256 EtM, or AEAD via libzuptsdk on the
|
|
||||||
.B --pq-sdk
|
|
||||||
path).
|
|
||||||
.IP \(bu 2
|
|
||||||
End-to-end byte-level tamper detection on encrypted archives. The F-09 byte-sweep regression (1827 positions on a representative archive, 2000 trials, every run) shows zero silent-accept positions.
|
|
||||||
.IP \(bu 2
|
|
||||||
Wrong-password and tampered-archive indistinguishability at the user-visible message layer (F-11). The default error wording is identical for both cases; only
|
|
||||||
.B --verbose
|
|
||||||
prints the distinguishing detail. This closes the "verbal probe-oracle" attack class where the error string leaked which check failed first.
|
|
||||||
.IP \(bu 2
|
|
||||||
Post-quantum forward secrecy on archives encrypted with
|
|
||||||
.B --pq-sdk
|
|
||||||
(assuming ML-KEM-768 holds against future quantum attack).
|
|
||||||
.IP \(bu 2
|
|
||||||
Archive-header and footer authentication via a 32-byte HMAC-SHA256 trailer (F-08). Tampering with the file count, comment offset, or timestamp is detected at archive open time.
|
|
||||||
.IP \(bu 2
|
|
||||||
Path-traversal at extract time. Entries with absolute paths or
|
|
||||||
.B ..
|
|
||||||
components are refused or stripped.
|
|
||||||
.RE
|
|
||||||
|
|
||||||
What it does
|
|
||||||
.B NOT
|
|
||||||
protect against:
|
|
||||||
|
|
||||||
.RS
|
|
||||||
.IP \(bu 2
|
|
||||||
Compromise of the endpoint that creates or restores the archive. If the host is compromised, the password, key file, or plaintext is accessible.
|
|
||||||
.IP \(bu 2
|
|
||||||
Compromise of the key file or password. Key custody is the user's responsibility.
|
|
||||||
.IP \(bu 2
|
|
||||||
A weak password. Argon2id with default parameters needs ~256 MiB and ~1 s to derive a key on commodity hardware; a 4-character password is still trivially crackable.
|
|
||||||
.IP \(bu 2
|
|
||||||
Metadata leakage. File names, sizes, and modification times are encrypted, but the archive's total size and the count of compressed blocks are visible to an observer.
|
|
||||||
.IP \(bu 2
|
|
||||||
Side channels on the compression layer (CRIME/BREACH-style). If the same archive contains both attacker-controlled and secret data and the attacker can observe the compressed size, length-based oracles may be possible.
|
|
||||||
.IP \(bu 2
|
|
||||||
Denial-of-service via malformed input on the decoder. The decoder rejects malformed input cleanly (no crashes in the fuzz harness), but a very large compressed input can still consume CPU and memory proportional to its size.
|
|
||||||
.RE
|
|
||||||
|
|
||||||
.SS Cryptographic primitives
|
|
||||||
|
|
||||||
.TS
|
|
||||||
tab(|);
|
|
||||||
l l.
|
|
||||||
SHA-256 | FIPS 180-4
|
|
||||||
SHA-3 / SHAKE | FIPS 202
|
|
||||||
ML-KEM-768 | FIPS 203
|
|
||||||
AES-256-CTR | NIST SP 800-38A
|
|
||||||
HMAC-SHA256 | RFC 2104 / FIPS 198-1
|
|
||||||
X25519 | RFC 7748
|
|
||||||
HKDF-SHA256 | RFC 5869
|
|
||||||
PBKDF2-SHA256 | RFC 8018
|
|
||||||
Argon2id | RFC 9106
|
|
||||||
XXH64 | non-cryptographic; used only inside the AEAD envelope
|
|
||||||
.TE
|
|
||||||
|
|
||||||
.SS Constant-time guarantees
|
|
||||||
|
|
||||||
All secret-dependent comparisons and table lookups in the cryptographic
|
|
||||||
core are constant-time. On x86_64 the hot paths (HMAC equality compare,
|
|
||||||
ML-KEM Fujisaki-Okamoto implicit rejection) are implemented in Jasmin
|
|
||||||
and assembled with
|
|
||||||
.BR jasminc (1).
|
|
||||||
On other architectures the same routines run in portable C; the
|
|
||||||
constant-time property is preserved by source-level construction.
|
|
||||||
|
|
||||||
.SH FILES
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.I ~/.config/vaptvupt/
|
|
||||||
Per-user configuration directory (reserved; not used in v3.0.0).
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.I /etc/vaptvupt/
|
|
||||||
System-wide configuration directory (reserved; not used in v3.0.0).
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.I /usr/share/bash-completion/completions/vaptvupt
|
|
||||||
Bash completion (and the symlinked legacy
|
|
||||||
.IR /usr/share/bash-completion/completions/zupt ).
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.I /usr/share/zsh/site-functions/_vaptvupt
|
|
||||||
zsh completion.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.I /usr/share/fish/vendor_completions.d/vaptvupt.fish
|
|
||||||
fish completion.
|
|
||||||
|
|
||||||
.SH ENVIRONMENT
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B VAPTVUPT_BIN
|
|
||||||
Override the binary path used by the GUI front-end. Legacy
|
|
||||||
.B ZUPT_BIN
|
|
||||||
is also honoured.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B VAPTVUPT_DEBUG
|
|
||||||
If set to any non-empty value, the GUI front-end prints its binary-discovery log to stderr.
|
|
||||||
|
|
||||||
.SH EXIT STATUS
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B 0
|
|
||||||
Success.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B 1
|
|
||||||
General error (bad arguments, file not found, etc.).
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B 2
|
|
||||||
Authentication failed. Wrong password, wrong key file, or the archive has been tampered with. Use
|
|
||||||
.B --verbose
|
|
||||||
to see the distinguishing detail (subject to F-11's threat model: detailed messages may leak which failure cause fired first).
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B 3
|
|
||||||
Archive-format error (wrong magic bytes, unsupported format version, corrupted header).
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B 4
|
|
||||||
I/O error (disk full, permission denied, network failure).
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B 5
|
|
||||||
Compressed-data integrity error (per-block HMAC mismatch detected mid-stream).
|
|
||||||
|
|
||||||
.SH PERFORMANCE
|
|
||||||
|
|
||||||
Numbers below are from the v3.0.0 release benchmark (May 2026), run
|
|
||||||
on an Intel Xeon @ 2.8 GHz with the codec compiled with the
|
|
||||||
distribution's default optimisation level.
|
|
||||||
|
|
||||||
.TS
|
|
||||||
tab(|);
|
|
||||||
l l l l l.
|
|
||||||
\fBFixture\fR | \fBTool\fR | \fBRatio\fR | \fBEnc MB/s\fR | \fBDec MB/s\fR
|
|
||||||
text 8 MB | vv-9 | 34.6% | 5.4 | 219
|
|
||||||
text 8 MB | gzip-9 | 30.9% | 6.2 | 137
|
|
||||||
text 8 MB | zstd-3 | 31.6% | 137 | 427
|
|
||||||
text 8 MB | zstd-19 | 25.5% | 1.6 | 384
|
|
||||||
source 670 KB | vv-9 | 25.1% | 11.4 | 128
|
|
||||||
source 670 KB | gzip-9 | 23.2% | 10.0 | 107
|
|
||||||
source 670 KB | zstd-3 | 24.1% | 91 | 160
|
|
||||||
binary 2.4 MB | vv-9 | 44.7% | 7.7 | 153
|
|
||||||
binary 2.4 MB | gzip-9 | 52.0% | 12.6 | 109
|
|
||||||
binary 2.4 MB | zstd-3 | 77.3% | 164 | 382
|
|
||||||
binary 2.4 MB | zstd-19 | 48.0% | 5.8 | 229
|
|
||||||
random 5 MB | vv-9 | 100.0% | 11.1 | 477
|
|
||||||
random 5 MB | zstd-3 | 100.0% | 397 | 681
|
|
||||||
.TE
|
|
||||||
|
|
||||||
.PP
|
|
||||||
Honest reading of these numbers:
|
|
||||||
|
|
||||||
.RS
|
|
||||||
.IP \(bu 2
|
|
||||||
On binary-structured data (game saves, mmap'd structures, struct arrays),
|
|
||||||
.B vaptvupt
|
|
||||||
beats zstd-3 by a wide margin on ratio (44.7% vs 77.3%) at the cost of being ~20\(mu slower to encode. For write-once / restore-often workloads this is the right trade.
|
|
||||||
.IP \(bu 2
|
|
||||||
On text and source, zstd-19 beats
|
|
||||||
.B vaptvupt
|
|
||||||
on ratio. The fundamental codec difference is that zstd's reference encoder has had years of compiler-engineering attention that
|
|
||||||
.B vaptvupt
|
|
||||||
has not.
|
|
||||||
.IP \(bu 2
|
|
||||||
Encode throughput is
|
|
||||||
.BR vaptvupt 's
|
|
||||||
weak axis. If encode latency matters more than ratio, use
|
|
||||||
.B -l 1
|
|
||||||
or
|
|
||||||
.BR -l 2 .
|
|
||||||
.IP \(bu 2
|
|
||||||
On random / already-compressed data, all codecs hit the incompressibility wall; comparing encode/decode throughput in that regime is mostly measuring memcpy speed plus framing overhead.
|
|
||||||
.RE
|
|
||||||
|
|
||||||
.SH EXAMPLES
|
|
||||||
|
|
||||||
.PP
|
|
||||||
Compress with default settings (PBKDF2-SHA256 password, VaptVupt level 7, multi-threaded):
|
|
||||||
|
|
||||||
.RS
|
|
||||||
.nf
|
|
||||||
$ vaptvupt compress -p secret backup.zupt ~/Documents
|
|
||||||
.fi
|
|
||||||
.RE
|
|
||||||
|
|
||||||
Compress with post-quantum hybrid encryption to a published public key:
|
|
||||||
|
|
||||||
.RS
|
|
||||||
.nf
|
|
||||||
$ vaptvupt keygen --sdk -o ~/.config/vaptvupt-mykey
|
|
||||||
$ vaptvupt keygen -o ~/.config/vaptvupt-mykey
|
|
||||||
$ vaptvupt keygen --pub -o mykey.pub -k ~/.config/vaptvupt-mykey
|
|
||||||
$ vaptvupt compress --pq mykey.pub backup.zupt ~/Documents
|
|
||||||
.fi
|
|
||||||
.RE
|
|
||||||
|
|
||||||
Compress with full (pure) post-quantum encryption — ML-KEM-768 only,
|
|
||||||
no classical component (compliance postures that mandate a single PQ
|
|
||||||
primitive):
|
|
||||||
|
|
||||||
.RS
|
|
||||||
.nf
|
|
||||||
$ vaptvupt keygen --pq-only -o pqkey
|
|
||||||
$ vaptvupt keygen --pub --pq-only -o pqkey.pub -k pqkey
|
|
||||||
$ vaptvupt compress --pq-only pqkey.pub backup.zupt ~/Documents
|
|
||||||
$ vaptvupt extract --pq-only pqkey -o restored backup.zupt
|
|
||||||
.fi
|
|
||||||
.RE
|
|
||||||
|
|
||||||
Backup a block device, sparse-aware:
|
|
||||||
|
|
||||||
.RS
|
|
||||||
.nf
|
|
||||||
$ sudo vaptvupt disk backup -p secret system.zupt /dev/nvme0n1p2
|
|
||||||
.fi
|
|
||||||
.RE
|
|
||||||
|
|
||||||
Verify an archive without restoring:
|
|
||||||
|
|
||||||
.RS
|
|
||||||
.nf
|
|
||||||
$ vaptvupt test -p secret backup.zupt
|
|
||||||
.fi
|
|
||||||
.RE
|
|
||||||
|
|
||||||
Print archive metadata without supplying a password:
|
|
||||||
|
|
||||||
.RS
|
|
||||||
.nf
|
|
||||||
$ vaptvupt info backup.zupt
|
|
||||||
.fi
|
|
||||||
.RE
|
|
||||||
|
|
||||||
Compare compression levels:
|
|
||||||
|
|
||||||
.RS
|
|
||||||
.nf
|
|
||||||
$ vaptvupt bench ~/Downloads/big-dataset.bin
|
|
||||||
.fi
|
|
||||||
.RE
|
|
||||||
|
|
||||||
Run the GUI from a desktop session where /usr/bin isn't on PATH (the bug fixed in v3.0.0):
|
|
||||||
|
|
||||||
.RS
|
|
||||||
.nf
|
|
||||||
$ VAPTVUPT_DEBUG=1 vaptvupt-gui 2> /tmp/discovery.log
|
|
||||||
.fi
|
|
||||||
.RE
|
|
||||||
|
|
||||||
.SH STANDARDS
|
|
||||||
|
|
||||||
ISO C11; POSIX.1-2017 for I/O and threading. The cryptographic
|
|
||||||
primitives implement the specifications listed in
|
|
||||||
.BR SECURITY
|
|
||||||
above. The on-disk archive format is documented in
|
|
||||||
.B FORMAT.md
|
|
||||||
in the source distribution.
|
|
||||||
|
|
||||||
.SH AUTHORS
|
|
||||||
Cristian Cezar Moisés <zupt@riseup.net> — primary author and maintainer.
|
|
||||||
|
|
||||||
.SH BUGS
|
|
||||||
Report bugs at https://git.securityops.co/cristiancmoises/vaptvupt/issues
|
|
||||||
or by email to <zupt@riseup.net>.
|
|
||||||
|
|
||||||
.SH LICENSE
|
|
||||||
AGPL-3.0-or-later for the application; GPL-3.0-or-later for the
|
|
||||||
embedded VaptVupt codec. Dual-licensed: a commercial licence is
|
|
||||||
available from <sac@securityops.co>.
|
|
||||||
|
|
||||||
.SH SEE ALSO
|
|
||||||
.BR vaptvupt-gui (1),
|
|
||||||
.BR zstd (1),
|
|
||||||
.BR gzip (1),
|
|
||||||
.BR xz (1),
|
|
||||||
.BR tar (1),
|
|
||||||
.BR cryptsetup (8),
|
|
||||||
.BR jasminc (1).
|
|
||||||
.PP
|
|
||||||
Project home: https://git.securityops.co/cristiancmoises/vaptvupt
|
|
||||||
.br
|
|
||||||
Threat model: see
|
|
||||||
.B THREAT_MODEL.md
|
|
||||||
in the source distribution.
|
|
||||||
.br
|
|
||||||
Archive format spec: see
|
|
||||||
.B FORMAT.md
|
|
||||||
in the source distribution.
|
|
||||||
234
doc/zupt-gui.1
234
doc/zupt-gui.1
|
|
@ -1,127 +1,157 @@
|
||||||
.\" Manpage for vaptvupt-gui (formerly zupt-gui; INPI Brasil trademark rename in v3.0.0)
|
|
||||||
.\" SPDX-License-Identifier: AGPL-3.0-or-later
|
.\" SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
.\" Copyright (c) 2025-2026 Cristian Cezar Moisés
|
.\" Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
.TH VAPTVUPT-GUI 1 "2026-06-11" "vaptvupt-gui 1.3.0" "User Commands"
|
.TH ZUPT-GUI 1 "2026-08-31" "ZUPT 5.2.2" "User Commands"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
vaptvupt-gui \- graphical interface for the VaptVupt post-quantum backup utility
|
zupt-gui \- Qt interface for the ZUPT backup utility
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B vaptvupt-gui
|
|
||||||
.RI [ ARCHIVE ]
|
|
||||||
.SH DESCRIPTION
|
|
||||||
.B vaptvupt-gui
|
|
||||||
is a graphical frontend for
|
|
||||||
.BR vaptvupt (1).
|
|
||||||
It provides tabs for compression, extraction, key management, and
|
|
||||||
full-disk backup. Both PQ encryption modes are exposed:
|
|
||||||
.B legacy --pq
|
|
||||||
and
|
|
||||||
.B SDK v2 --pq-sdk
|
|
||||||
(HKDF combiner, key commitment, HPKE binding, Argon2id).
|
|
||||||
|
|
||||||
The legacy command name
|
|
||||||
.B zupt-gui
|
.B zupt-gui
|
||||||
is preserved as a symlink for backward compatibility; both invocations
|
.RI [ ARCHIVE.zupt ]
|
||||||
behave identically.
|
.br
|
||||||
|
.B zupt-gui
|
||||||
If
|
.BI --compress " FILE ..."
|
||||||
.I ARCHIVE
|
.br
|
||||||
is given on the command line, the GUI opens directly on the
|
.B zupt-gui
|
||||||
extract tab with that archive preloaded.
|
.BI --extract " ARCHIVE.zupt"
|
||||||
|
.br
|
||||||
.B vaptvupt-gui
|
.B zupt-gui
|
||||||
uses Qt 6. It works with either of the following Python Qt bindings,
|
.RB [ --help | --version | --selftest ]
|
||||||
auto-detected at startup in this order:
|
.SH DESCRIPTION
|
||||||
.IP \(bu 2
|
.B zupt-gui
|
||||||
PySide6 (Qt for Python)
|
is a Python Qt 6 frontend for
|
||||||
.IP \(bu 2
|
.BR zupt (1).
|
||||||
PyQt6
|
It creates, inspects, verifies, and extracts archives by running the CLI as a
|
||||||
|
child process. It can also request CLI disk backup and restore operations.
|
||||||
|
The GUI does not implement an archive codec or cryptography itself.
|
||||||
.PP
|
.PP
|
||||||
If neither is installed, the GUI prints an instructive error and exits.
|
PySide6 is tried first and PyQt6 is used as a fallback. The selected
|
||||||
|
.B zupt
|
||||||
|
command is checked by executing
|
||||||
|
.BR "zupt version" .
|
||||||
|
The ZUPT command and environment variables are preferred; renamed-era names
|
||||||
|
are accepted only for compatibility with an existing installation.
|
||||||
|
.PP
|
||||||
|
When paired with the source-only baseline CLI, the frontend uses a build with
|
||||||
|
.B WITH_SDK=0
|
||||||
|
and
|
||||||
|
.BR WITH_PQBOX=0 .
|
||||||
|
Native password,
|
||||||
|
.B --pq
|
||||||
|
(ML-KEM-768 plus X25519), and
|
||||||
|
.B --pq-only
|
||||||
|
(ML-KEM-768) modes remain available. The GUI parses the CLI's
|
||||||
|
.B Build integrations:
|
||||||
|
line and exposes
|
||||||
|
.B --pq-sdk
|
||||||
|
or
|
||||||
|
.B --pq-box
|
||||||
|
only when libvuptsdk or libpqvaptvupt is independently reported enabled.
|
||||||
|
These two optional integrations are detected separately.
|
||||||
|
.PP
|
||||||
|
The gated 5.2.2 GUI release set is limited to the architecture-independent DEB,
|
||||||
|
noarch/source RPM, and source-only portable ZIP named in the project README.
|
||||||
|
Package gates require exact checks and installed off-screen GUI/CLI integration.
|
||||||
|
The portable ZIP receives source scans, an exact safe-member allowlist, and an
|
||||||
|
extracted launcher test; it bundles no Python, Qt, CLI, or compiled runtime.
|
||||||
|
AppImage, AppDir and Flatpak bundles and Windows/macOS GUI installers are not
|
||||||
|
promoted; the Windows ZIP and macOS DMG are CLI-only.
|
||||||
|
.PP
|
||||||
|
The GUI does not expose the CLI's recovery-only
|
||||||
|
.B --allow-legacy-no-ait
|
||||||
|
option. A known, trusted pre-AIT archive must be recovered explicitly with the
|
||||||
|
CLI; untrusted trailerless archives must remain rejected.
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
.B --compress
|
||||||
|
Open the Compress tab with the remaining arguments selected as inputs.
|
||||||
|
.TP
|
||||||
|
.B --extract
|
||||||
|
Open the Extract tab with the following archive selected.
|
||||||
|
.TP
|
||||||
|
.B --selftest
|
||||||
|
Create the complete interface, run the event loop briefly, and exit. A display
|
||||||
|
backend (or a suitable off-screen Qt backend) is still required.
|
||||||
|
.TP
|
||||||
|
.BR --version , " -V"
|
||||||
|
Print the GUI, Qt binding, CLI version, and selected CLI path.
|
||||||
|
.TP
|
||||||
|
.BR --help , " -h"
|
||||||
|
Print command-line usage.
|
||||||
.SH TABS
|
.SH TABS
|
||||||
.TP
|
.TP
|
||||||
|
.B Keys
|
||||||
|
Generate and export recipient keys. Mode choices follow CLI capability
|
||||||
|
detection, including independent SDK and PQ-box choices when enabled.
|
||||||
|
.TP
|
||||||
.B Compress
|
.B Compress
|
||||||
Select files or directories, choose codec, level, password and/or PQ
|
Choose inputs, destination, codec options, password, and an optional recipient
|
||||||
key. The
|
public key.
|
||||||
.B Mode
|
|
||||||
panel controls whether the SDK v2 path or the legacy path is used.
|
|
||||||
.TP
|
.TP
|
||||||
.B Extract
|
.B Extract
|
||||||
Open a .zupt archive, select output directory, provide password
|
Choose an archive, output directory, and any required password or private key.
|
||||||
and/or PQ private key.
|
The GUI uses
|
||||||
|
.B zupt info
|
||||||
|
to auto-detect supported archive protection modes. That framing inspection is
|
||||||
|
unauthenticated and is only a mode-selection hint; the subsequent CLI extract
|
||||||
|
or test operation performs the required AIT and content validation.
|
||||||
.TP
|
.TP
|
||||||
.B Keygen
|
.B Verify
|
||||||
Generate ML-KEM-768 + X25519 keypair. The
|
Inspect an archive header or run the CLI integrity test with the detected
|
||||||
.B SDK v2 format
|
credential type.
|
||||||
checkbox controls whether the keypair is generated via
|
|
||||||
.B vaptvupt keygen --sdk
|
|
||||||
(producing
|
|
||||||
.IR file
|
|
||||||
and
|
|
||||||
.IR file.pub
|
|
||||||
in one step) or via the legacy
|
|
||||||
.BR "vaptvupt keygen" .
|
|
||||||
.TP
|
.TP
|
||||||
.B Disk
|
.B Disk
|
||||||
Full-disk backup and restore. Enumerates block devices with
|
Request full-device or image backup and restore through the CLI. Raw devices
|
||||||
human-readable sizes. Same encryption mode controls as Compress.
|
may require operating-system privileges. Restore overwrites its selected target
|
||||||
|
and requires explicit confirmation in the GUI.
|
||||||
.SH FILES
|
|
||||||
.TP
|
.TP
|
||||||
.I /usr/bin/vaptvupt-gui
|
.B About
|
||||||
Wrapper script that invokes the Python entry point (and the symlinked
|
Show the detected CLI version and build information.
|
||||||
legacy
|
|
||||||
.IR /usr/bin/zupt-gui ).
|
|
||||||
.TP
|
|
||||||
.I /usr/lib/vaptvupt-gui/zupt_gui.py
|
|
||||||
Main Python source.
|
|
||||||
.TP
|
|
||||||
.I /usr/share/applications/vaptvupt-gui.desktop
|
|
||||||
Desktop entry for menu integration.
|
|
||||||
.TP
|
|
||||||
.I /usr/share/icons/hicolor/256x256/apps/vaptvupt-gui.png
|
|
||||||
Application icon.
|
|
||||||
|
|
||||||
.SH ENVIRONMENT
|
.SH ENVIRONMENT
|
||||||
.TP
|
.TP
|
||||||
.B VAPTVUPT_BIN
|
|
||||||
Override the path to the
|
|
||||||
.B vaptvupt
|
|
||||||
binary (default: search
|
|
||||||
.IR PATH ).
|
|
||||||
The legacy name
|
|
||||||
.B ZUPT_BIN
|
.B ZUPT_BIN
|
||||||
is also honoured.
|
Absolute or executable path to the preferred
|
||||||
|
.B zupt
|
||||||
|
command. It must pass the CLI version liveness check.
|
||||||
.TP
|
.TP
|
||||||
.B VAPTVUPT_DEBUG
|
|
||||||
Enable binary-discovery debug logging on stderr. The legacy name
|
|
||||||
.B ZUPT_DEBUG
|
.B ZUPT_DEBUG
|
||||||
is also honoured.
|
Print command-discovery diagnostics to standard error when non-empty.
|
||||||
|
.TP
|
||||||
|
.B ZUPT_NO_XCB_FALLBACK
|
||||||
|
Disable the guarded XWayland relaunch used when a Wayland window is never
|
||||||
|
exposed.
|
||||||
|
.TP
|
||||||
|
.BR VAPTVUPT_BIN , " VAPTVUPT_DEBUG" , " VAPTVUPT_NO_XCB_FALLBACK"
|
||||||
|
Renamed-era compatibility aliases for the corresponding ZUPT variables.
|
||||||
|
New integrations should use the ZUPT names.
|
||||||
|
.SH FILES
|
||||||
|
.TP
|
||||||
|
.I /usr/bin/zupt-gui
|
||||||
|
Installed launcher.
|
||||||
|
.TP
|
||||||
|
.I /usr/lib/zupt-gui/zupt_gui.py
|
||||||
|
GUI source location used by the DEB or source installer. The noarch RPM may use
|
||||||
|
an architecture-independent shared-data directory instead.
|
||||||
|
.TP
|
||||||
|
.I /usr/share/applications/zupt-gui.desktop
|
||||||
|
Desktop entry.
|
||||||
|
.PP
|
||||||
|
Distribution packages do not install a
|
||||||
|
.B vaptvupt-gui
|
||||||
|
alias. The source installer can create one only with its explicit
|
||||||
|
.B --legacy-alias
|
||||||
|
option. The optional alias has no separate manual page.
|
||||||
.SH BUGS
|
.SH BUGS
|
||||||
Report at
|
Report reproducible issues at
|
||||||
.UR https://git.securityops.co/cristiancmoises/vaptvupt/issues
|
.UR https://github.com/cristiancmoises/zupt/issues
|
||||||
.UE .
|
.UE .
|
||||||
|
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Cristian Cezar Moisés
|
Cristian Cezar Moisés
|
||||||
.MT zupt@riseup.net
|
|
||||||
.ME
|
|
||||||
|
|
||||||
.SH SEE ALSO
|
|
||||||
.BR vaptvupt (1).
|
|
||||||
|
|
||||||
.SH LICENSE
|
.SH LICENSE
|
||||||
.PP
|
The current integrated GUI source carries AGPL-3.0-or-later notices. Published
|
||||||
vaptvupt-gui is licensed under the
|
historical revisions include MIT grants that remain applicable to the exact
|
||||||
.B GNU Affero General Public License version 3 or later
|
material distributed under them. See
|
||||||
(AGPL-3.0-or-later). Commercial license available for relief from
|
.I gui/LICENSE-GUI
|
||||||
copyleft terms; contact
|
and the 5.2.2 licensing erratum in
|
||||||
.MT sac@securityops.co
|
.I CHANGELOG.md
|
||||||
.ME .
|
for scope and repository evidence.
|
||||||
|
.SH SEE ALSO
|
||||||
.SH PROJECT
|
.BR zupt (1)
|
||||||
.PP
|
|
||||||
Home page:
|
|
||||||
.UR https://git.securityops.co/cristiancmoises/vaptvupt
|
|
||||||
.UE
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
vaptvupt.1
|
|
||||||
634
doc/zupt.1
Normal file
634
doc/zupt.1
Normal file
|
|
@ -0,0 +1,634 @@
|
||||||
|
.\" SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
.\" Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
|
.TH ZUPT 1 "2026-08-31" "ZUPT 5.2.2" "User Commands"
|
||||||
|
.
|
||||||
|
.SH NAME
|
||||||
|
zupt \- source-built backup compression and authenticated-encryption utility
|
||||||
|
.
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B zupt compress
|
||||||
|
.RI [ options ]
|
||||||
|
.I output.zupt input...
|
||||||
|
.br
|
||||||
|
.B zupt extract
|
||||||
|
.RI [ options ]
|
||||||
|
.I archive.zupt
|
||||||
|
.br
|
||||||
|
.B zupt list
|
||||||
|
.RI [ options ]
|
||||||
|
.I archive.zupt
|
||||||
|
.br
|
||||||
|
.B zupt test
|
||||||
|
.RI [ options ]
|
||||||
|
.I archive.zupt
|
||||||
|
.br
|
||||||
|
.B zupt info
|
||||||
|
.I archive.zupt
|
||||||
|
.br
|
||||||
|
.B zupt bench
|
||||||
|
.RB [ --compare ]
|
||||||
|
.I input...
|
||||||
|
.br
|
||||||
|
.B zupt disk backup
|
||||||
|
.RI [ options ]
|
||||||
|
.I output.zupt device-or-file
|
||||||
|
.br
|
||||||
|
.B zupt disk restore
|
||||||
|
.RI [ options ]
|
||||||
|
.I archive.zupt target-device-or-file
|
||||||
|
.br
|
||||||
|
.B zupt keygen
|
||||||
|
.RI [ key-options ]
|
||||||
|
.B -o
|
||||||
|
.I output
|
||||||
|
.br
|
||||||
|
.B zupt
|
||||||
|
.RB { help | --help | -h | version | --version | -V }
|
||||||
|
.
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.B zupt
|
||||||
|
creates self-contained backup archives with the historical
|
||||||
|
.B .zupt
|
||||||
|
extension. Version 5.2.2 restores the original product name, ZUPT, and the
|
||||||
|
primary installed command is
|
||||||
|
.BR zupt .
|
||||||
|
The archive extension and
|
||||||
|
.B ZUPT
|
||||||
|
format magic, codec identifiers, and archive compatibility remain unchanged.
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
Plain archives provide compression checksums for accidental-corruption
|
||||||
|
detection. They do not provide cryptographic authentication against an attacker
|
||||||
|
who can rewrite an archive. Encrypted archives use AES-256-CTR with
|
||||||
|
HMAC-SHA256 and authenticate current per-block framing, logical frame position,
|
||||||
|
and archive metadata. The validating read paths require an archive-integrity
|
||||||
|
trailer by default; a no-trailer archive fails closed unless the caller selects
|
||||||
|
the explicit trusted-legacy override described below.
|
||||||
|
See
|
||||||
|
.B SECURITY.md
|
||||||
|
and
|
||||||
|
.B THREAT_MODEL.md
|
||||||
|
for the exact boundary and historical-format limitations.
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
The bundled compression codec is VaptVupt codec 2.65.3.
|
||||||
|
Automatic codec selection uses VaptVupt where the supported AVX2 or NEON path is available and
|
||||||
|
uses the portable LZHP codec otherwise. Use a codec-selection option only when
|
||||||
|
a specific choice is required.
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
The renamed-era
|
||||||
|
.B vaptvupt
|
||||||
|
command is an optional compatibility alias and is not installed by default.
|
||||||
|
Distribution packages, including the openSUSE main package, expose
|
||||||
|
.B zupt
|
||||||
|
as the canonical command.
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
Git and the upstream source tarball are source-only. Separately built CLI DEB,
|
||||||
|
binary RPM, SRPM, notice-bearing Linux tar.xz, Windows ZIP, and macOS DMG assets
|
||||||
|
may be published from the immutable tag only after their target-specific gates
|
||||||
|
pass; they never enter Git or the source tarball. An AppImage is not promoted
|
||||||
|
for 5.2.2; neither are AppDir/Flatpak bundles, GUI platform installers, or bare
|
||||||
|
Linux/Windows executables. The Python/Qt frontend remains available as source;
|
||||||
|
its gated architecture-independent DEB, noarch/source RPM, and source-only
|
||||||
|
portable ZIP are included in the release claim. The portable ZIP contains no
|
||||||
|
Python, Qt, CLI, or compiled runtime. Windows and macOS artifacts remain
|
||||||
|
CLI-only.
|
||||||
|
.
|
||||||
|
.SH COMMANDS
|
||||||
|
.TP
|
||||||
|
.BR compress , " c"
|
||||||
|
Create an archive from one or more files or directories. Directories are
|
||||||
|
traversed recursively. All options must precede
|
||||||
|
.IR output.zupt .
|
||||||
|
A literal input name beginning with a hyphen can follow a
|
||||||
|
.B --
|
||||||
|
separator after the output name.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR extract , " x"
|
||||||
|
Extract regular-file contents below the current directory or the directory
|
||||||
|
selected by
|
||||||
|
.BR -o .
|
||||||
|
Entry names are validated and resolved below a pinned destination. Existing
|
||||||
|
destination files are not overwritten. Decoded data is published from a
|
||||||
|
private temporary file only after its expected size and checksum are verified.
|
||||||
|
ZUPT does not restore ownership, ACLs, extended attributes, original mode,
|
||||||
|
or modification time.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR list , " l"
|
||||||
|
Open and verify the archive as required, then print its format, block and
|
||||||
|
protection flags followed by each entry's path, original size, compressed size,
|
||||||
|
and compression ratio. No file is extracted.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR test , " t"
|
||||||
|
Decode and verify the archive without writing extracted files. Exit status is
|
||||||
|
nonzero on an authentication, integrity, format, or I/O failure.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR info , " i"
|
||||||
|
Read non-secret framing metadata without a password or private key. Current
|
||||||
|
output includes archive size, format version, integrity-trailer type, UUID,
|
||||||
|
creation timestamp, block count when a footer is found, encryption/PQ mode,
|
||||||
|
selected global feature flags, and whether a comment is present. This command
|
||||||
|
does not decrypt, list, or extract entries. It does not validate the trailer or
|
||||||
|
archive contents: a reported trailer is framing information, and successful
|
||||||
|
execution is not an integrity result. Its input must still be treated as
|
||||||
|
untrusted.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR bench , " b"
|
||||||
|
Run the built-in compression-level benchmark on supplied input. With
|
||||||
|
.BR --compare ,
|
||||||
|
also compare external compressors available on the host. Results depend on the
|
||||||
|
input, compiler, CPU, storage, and system load and are not release support
|
||||||
|
claims.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.B disk backup
|
||||||
|
Read a device or regular file sequentially and create a sparse-aware archive.
|
||||||
|
Zero regions are represented without storing their full contents.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.B disk restore
|
||||||
|
Restore a disk-image archive to a device or regular file. The target is written
|
||||||
|
destructively; verify both operands and use the least privilege required. The
|
||||||
|
archive is first copied to a private, auto-deleted scratch file, and validation
|
||||||
|
and restoration consume that same snapshot before the target is opened. Set
|
||||||
|
.B ZUPT_TMPDIR
|
||||||
|
to an existing private scratch directory when needed; an invalid override fails
|
||||||
|
without fallback. A raw block device is rejected before the first write when
|
||||||
|
its capacity cannot be determined or is smaller than the restored image.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.B keygen
|
||||||
|
Generate a native ML-KEM-768 plus X25519 hybrid private key by default. Other
|
||||||
|
key formats require the matching key-generation option and, for optional SDK or
|
||||||
|
PQBOX modes, a build with the corresponding system integration enabled.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR version , " --version" , " -V"
|
||||||
|
Print the program, archive-format and codec versions, compiled optional
|
||||||
|
integrations, runtime CPU acceleration, license scopes, and canonical project
|
||||||
|
URL.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR help , " --help" , " -h"
|
||||||
|
Print command usage and the options compiled into the program.
|
||||||
|
.
|
||||||
|
.SH PASSWORD INPUT
|
||||||
|
The password options are accepted by
|
||||||
|
.BR compress ,
|
||||||
|
.BR extract ,
|
||||||
|
.BR list ,
|
||||||
|
.BR test ,
|
||||||
|
and both
|
||||||
|
.B disk
|
||||||
|
subcommands. For
|
||||||
|
.B compress
|
||||||
|
and
|
||||||
|
.BR "disk backup" ,
|
||||||
|
the interactive form asks for confirmation.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR -p " " password , " --password " password
|
||||||
|
Read a password directly from the next process argument. This is compatible
|
||||||
|
with older command lines but can expose the password through shell history or
|
||||||
|
process inspection. Prefer one of the non-argv forms below.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.B --password-prompt
|
||||||
|
Read the password from the terminal without echo. This explicit form avoids
|
||||||
|
the optional-argument ambiguity of historical
|
||||||
|
.BR -p .
|
||||||
|
On POSIX, handled interruptions restore the terminal settings saved before the
|
||||||
|
prompt.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BI --pass-file " file"
|
||||||
|
Read the first line from
|
||||||
|
.IR file .
|
||||||
|
The trailing LF and an optional preceding CR are removed. Empty input, an
|
||||||
|
embedded NUL, or an overlong password is rejected. Protect the file with
|
||||||
|
restrictive permissions and remove it securely when it is no longer needed.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BI --pass-fd " fd"
|
||||||
|
Read the first line from the inherited numeric file descriptor
|
||||||
|
.IR fd .
|
||||||
|
ZUPT duplicates the descriptor for reading and does not close the caller's
|
||||||
|
original descriptor. The duplicate shares the underlying stream and offset;
|
||||||
|
buffered input may consume beyond the password line, so dedicate the descriptor
|
||||||
|
to this read. The same input validation as
|
||||||
|
.B --pass-file
|
||||||
|
applies.
|
||||||
|
.
|
||||||
|
.SH COMPRESSION OPTIONS
|
||||||
|
These options are accepted by
|
||||||
|
.B compress
|
||||||
|
unless stated otherwise.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR -l " " 1..9 , " --level " 1..9
|
||||||
|
Set the compression level. The default is 7. Automatic block sizes are 128 KiB
|
||||||
|
for levels 1-2, 1 MiB for levels 3-4, 2 MiB for levels 5-6, 4 MiB for level 7,
|
||||||
|
and 8 MiB for levels 8-9. Deduplication uses a smaller automatic granularity.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR -b " " bytes , " --block " bytes
|
||||||
|
Set the block size in bytes. Values are constrained to the supported range of
|
||||||
|
64 KiB through 256 MiB.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR -s , " --store"
|
||||||
|
Store data without compression.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR -f , " --fast"
|
||||||
|
Select the fast LZ codec.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR --vv , " --vaptvupt"
|
||||||
|
Force the bundled VaptVupt LZ plus ANS codec.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.B --lzhp
|
||||||
|
Force the portable LZHP codec.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BI --kdf " algorithm"
|
||||||
|
Select the password KDF. The default source-only build supports and defaults to
|
||||||
|
.BR pbkdf2 ,
|
||||||
|
using PBKDF2-SHA256 with 600,000 iterations. A
|
||||||
|
.B WITH_SDK=1
|
||||||
|
build additionally supports
|
||||||
|
.B argon2id
|
||||||
|
and uses it by default. A build must reject a requested KDF that it cannot
|
||||||
|
provide rather than silently changing algorithms.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR -c " " text , " --comment " text
|
||||||
|
Store a comment of up to 4095 CLI bytes. In an encrypted archive the comment is
|
||||||
|
encrypted and authenticated with the archive; in a plain archive it receives
|
||||||
|
only the plain archive's non-cryptographic integrity treatment.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BI --comment-file " file"
|
||||||
|
Read the comment from
|
||||||
|
.IR file .
|
||||||
|
Trailing CR/LF characters are removed.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR -D , " --dedup"
|
||||||
|
Enable block-level deduplication. Encrypted deduplicated blocks still use fresh
|
||||||
|
per-block nonces in current archives. DATA and DEDUP_REF frames are bound to
|
||||||
|
their logical positions. An authenticated reference also carries the source
|
||||||
|
position required to authenticate the referenced DATA frame.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR -S , " --solid"
|
||||||
|
Use one solid compression stream. Solid mode is single-threaded.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR -y , " --force"
|
||||||
|
Allow
|
||||||
|
.B compress
|
||||||
|
to overwrite an existing output whose name does not end in
|
||||||
|
.BR .zupt .
|
||||||
|
This does not relax extraction's no-overwrite policy.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR -t " " count , " --threads " count
|
||||||
|
Set the compression or extraction thread count. Zero selects automatic
|
||||||
|
detection; explicit values are limited to 64. This option is accepted by
|
||||||
|
.B compress
|
||||||
|
and
|
||||||
|
.BR extract ,
|
||||||
|
and by disk backup/restore, but not by
|
||||||
|
.B list
|
||||||
|
or
|
||||||
|
.BR test .
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR -v , " --verbose"
|
||||||
|
Enable additional progress or diagnostic output where the selected command
|
||||||
|
implements it. Authentication failures remain intentionally generic unless
|
||||||
|
verbose diagnostics are requested, reducing the default verbal probe-oracle.
|
||||||
|
.
|
||||||
|
.SH POST-QUANTUM OPTIONS
|
||||||
|
.TP
|
||||||
|
.BI --pq " key"
|
||||||
|
Use the native hybrid envelope: ML-KEM-768 plus X25519 with the archive-key
|
||||||
|
combiner documented in
|
||||||
|
.BR THREAT_MODEL.md .
|
||||||
|
Use the recipient public key for creation and the matching private key for
|
||||||
|
reading. This is the recommended native PQ mode.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BI --pq-only " key"
|
||||||
|
Use native ML-KEM-768 without the X25519 hedge. Choose it only when a policy
|
||||||
|
requires a single post-quantum KEM. Its keys are generated with
|
||||||
|
.BR "zupt keygen --pq-only" .
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BI --pq-sdk " key"
|
||||||
|
Use the optional system
|
||||||
|
.B libvuptsdk
|
||||||
|
integration. This option is unavailable unless ZUPT was built with
|
||||||
|
.BR WITH_SDK=1 .
|
||||||
|
It is not enabled by the default source-only distribution build.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BI --pq-box " key"
|
||||||
|
Use the optional system
|
||||||
|
.B libpqvaptvupt
|
||||||
|
sealed-box integration. This option is unavailable unless ZUPT was built
|
||||||
|
with
|
||||||
|
.BR WITH_PQBOX=1 .
|
||||||
|
It is not enabled by the default source-only distribution build.
|
||||||
|
.
|
||||||
|
.SH EXTRACTION OPTIONS
|
||||||
|
.TP
|
||||||
|
.BR -o " " directory , " --output " directory
|
||||||
|
Extract below
|
||||||
|
.IR directory .
|
||||||
|
The directory is created when needed. The default is the current directory.
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
Extraction also accepts the password, PQ,
|
||||||
|
.BR -v ,
|
||||||
|
and
|
||||||
|
.B -t
|
||||||
|
options described above. Unlike compression, extract/list/test options may
|
||||||
|
appear before or after the single archive operand.
|
||||||
|
.
|
||||||
|
.SH TRUSTED LEGACY READ OPTION
|
||||||
|
.TP
|
||||||
|
.B --allow-legacy-no-ait
|
||||||
|
Permit recovery of a known, trusted historical archive that predates the
|
||||||
|
archive-integrity trailer. By default the validating read commands reject an
|
||||||
|
archive without an AIT, without trusting its unauthenticated header flags. This
|
||||||
|
option emits a downgrade warning and is accepted only by
|
||||||
|
.BR extract ,
|
||||||
|
.BR list ,
|
||||||
|
.BR test ,
|
||||||
|
and
|
||||||
|
.BR "disk restore" .
|
||||||
|
It is rejected by compression and disk backup, which always write a current
|
||||||
|
trailer.
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
Do not use this option for an archive obtained from untrusted or
|
||||||
|
attacker-writable storage. It does not authenticate the legacy header or footer.
|
||||||
|
After recovery, verify the restored data and create a new current archive.
|
||||||
|
.
|
||||||
|
.SH DISK OPTIONS
|
||||||
|
Disk options must precede the archive and device/file operands. Both disk
|
||||||
|
subcommands accept
|
||||||
|
.BR -l / --level ,
|
||||||
|
.BR -b / --block ,
|
||||||
|
.BR -s / --store ,
|
||||||
|
.BR --vv / --vaptvupt ,
|
||||||
|
.BR --lzhp ,
|
||||||
|
the password input options,
|
||||||
|
.BR --pq ,
|
||||||
|
.BR --pq-only ,
|
||||||
|
.BR -D / --dedup ,
|
||||||
|
.BR -c / --comment ,
|
||||||
|
.BR --comment-file ,
|
||||||
|
.BR --kdf ,
|
||||||
|
.BR -t / --threads ,
|
||||||
|
and
|
||||||
|
.BR -v / --verbose .
|
||||||
|
The optional SDK/PQBOX envelope options and solid mode are not disk-command
|
||||||
|
options.
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
.B --allow-legacy-no-ait
|
||||||
|
is accepted by disk restore only.
|
||||||
|
.
|
||||||
|
.SH KEY GENERATION OPTIONS
|
||||||
|
.TP
|
||||||
|
.BR -o " " file , " --output " file
|
||||||
|
Write the generated private key or exported public key to
|
||||||
|
.IR file .
|
||||||
|
This option is required.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR --pub
|
||||||
|
Export a public key from the private key selected by
|
||||||
|
.BR -k .
|
||||||
|
Use the same mode option as the private key.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR -k " " file , " --key " file
|
||||||
|
Read the source private key used by
|
||||||
|
.BR --pub .
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR --pq-only , " --pqonly"
|
||||||
|
Generate or export a native ML-KEM-768-only key.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR --sdk , " --pq-sdk"
|
||||||
|
Generate an SDK-format key through the optional
|
||||||
|
.B libvuptsdk
|
||||||
|
integration.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.BR --box , " --pq-box"
|
||||||
|
Generate a sealed-box key through the optional
|
||||||
|
.B libpqvaptvupt
|
||||||
|
integration.
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
With no mode option,
|
||||||
|
.B keygen
|
||||||
|
generates or exports the native ML-KEM-768 plus X25519 hybrid format used by
|
||||||
|
.BR --pq .
|
||||||
|
.PP
|
||||||
|
Private-key output uses no-replace creation, POSIX mode 0600, or a Windows
|
||||||
|
current-user-only DACL. A write, flush, or close failure leaves the invalid
|
||||||
|
exclusive partial for manual removal rather than unlinking a possibly replaced
|
||||||
|
pathname. Native ZKEY and ZPQK inputs must have a valid checksum, version,
|
||||||
|
flags, reserved bytes, exact size, and public/private role; malformed or
|
||||||
|
role-confused keys are rejected.
|
||||||
|
.
|
||||||
|
.SH SECURITY NOTES
|
||||||
|
Use encrypted archives when an attacker may modify storage. Plain checksums are
|
||||||
|
not authentication. Keep private keys separate from archives, use high-entropy
|
||||||
|
passwords, and prefer
|
||||||
|
.BR --password-prompt ,
|
||||||
|
.BR --pass-file ,
|
||||||
|
or
|
||||||
|
.B --pass-fd
|
||||||
|
over argv passwords.
|
||||||
|
.PP
|
||||||
|
Archive comments remain untrusted display data even when authenticated. ZUPT
|
||||||
|
renders control bytes without emitting raw terminal-control sequences.
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
Extract untrusted archives as a dedicated unprivileged user into a new empty
|
||||||
|
local directory. POSIX builds canonicalize the user-selected output root once,
|
||||||
|
then traverse below a pinned directory descriptor with no-follow operations.
|
||||||
|
Windows builds use handle-relative traversal and
|
||||||
|
no-replace publication for normal local Win32 destinations. Extended-length and
|
||||||
|
device-namespace paths, raw UNC output roots, and mapped/network-drive output
|
||||||
|
are not supported in 5.2.2. Cross-compilation and Wine results are not native
|
||||||
|
Windows evidence; the native Windows package gate, including its Unicode round
|
||||||
|
trip, is separate and mandatory before publication.
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
Current encrypted archives protect ciphertext, canonical framing metadata,
|
||||||
|
logical frame position, and current header/footer metadata. The default AIT
|
||||||
|
requirement prevents a silent downgrade to a trailerless layout; the explicit
|
||||||
|
legacy override intentionally leaves old header/footer metadata outside that
|
||||||
|
authenticated boundary. ZUPT does not protect a compromised endpoint, a
|
||||||
|
disclosed credential, archive rollback or deletion, traffic analysis,
|
||||||
|
compression-length side channels, or every compiler and microarchitectural side
|
||||||
|
channel. Native PQ archive encryption is at-rest encryption, not session
|
||||||
|
forward secrecy.
|
||||||
|
.
|
||||||
|
.SH EXIT STATUS
|
||||||
|
.TP
|
||||||
|
.B 0
|
||||||
|
The requested operation completed successfully.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.B 1
|
||||||
|
Invalid arguments or an operational, format, authentication, integrity, or I/O
|
||||||
|
failure.
|
||||||
|
.
|
||||||
|
.SH FILES
|
||||||
|
.TP
|
||||||
|
.I /usr/bin/zupt
|
||||||
|
Distribution-installed command.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.I /usr/share/bash-completion/completions/zupt
|
||||||
|
Bash completion for the current command.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.I /usr/share/zsh/site-functions/_zupt
|
||||||
|
zsh completion for the current command.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
|
.I /usr/share/fish/vendor_completions.d/zupt.fish
|
||||||
|
fish completion for the current command.
|
||||||
|
.
|
||||||
|
.SH EXAMPLES
|
||||||
|
Create a plain archive:
|
||||||
|
.PP
|
||||||
|
.RS
|
||||||
|
.B zupt compress backup.zupt Documents/
|
||||||
|
.RE
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
Create and restore a password-protected archive without placing the password in
|
||||||
|
argv:
|
||||||
|
.PP
|
||||||
|
.RS
|
||||||
|
.B zupt compress --password-prompt secure.zupt Documents/
|
||||||
|
.br
|
||||||
|
.B zupt test --password-prompt secure.zupt
|
||||||
|
.br
|
||||||
|
.B zupt extract --password-prompt -o restored secure.zupt
|
||||||
|
.RE
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
Use a password supplied on file descriptor 3:
|
||||||
|
.PP
|
||||||
|
.RS
|
||||||
|
.B zupt test --pass-fd 3 secure.zupt 3<password-file
|
||||||
|
.RE
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
Create a native hybrid recipient key and archive:
|
||||||
|
.PP
|
||||||
|
.RS
|
||||||
|
.B zupt keygen -o recipient.key
|
||||||
|
.br
|
||||||
|
.B zupt keygen --pub -k recipient.key -o recipient.pub
|
||||||
|
.br
|
||||||
|
.B zupt compress --pq recipient.pub backup.zupt Documents/
|
||||||
|
.br
|
||||||
|
.B zupt extract --pq recipient.key -o restored backup.zupt
|
||||||
|
.RE
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
Inspect and verify an archive without extraction:
|
||||||
|
.PP
|
||||||
|
.RS
|
||||||
|
.B zupt info backup.zupt
|
||||||
|
.br
|
||||||
|
.B zupt list backup.zupt
|
||||||
|
.br
|
||||||
|
.B zupt test backup.zupt
|
||||||
|
.RE
|
||||||
|
.
|
||||||
|
.SH COMPATIBILITY
|
||||||
|
The on-disk version byte remains v1.6, with flag-gated 5.2.2 encodings for
|
||||||
|
positional authenticated dedup references and disk-image integrity metadata.
|
||||||
|
The 5.2.2 reader retains compatibility parsers for the fixed-width disk index
|
||||||
|
and the encrypted-dedup linear AAD sequence published through 5.2.1. The narrow
|
||||||
|
compatibility fixture is an actual v5.2.1 password-encrypted DATA/DATA/REF/DATA disk
|
||||||
|
archive stored as hexadecimal text with source and hash provenance. The
|
||||||
|
candidate lists, tests, extracts, and restores that fixture byte-exact, and the exact
|
||||||
|
final candidate must repeat the gate. It does not imply that a 5.2.1 reader
|
||||||
|
accepts every new 5.2.2 archive or that every historical encrypted mode was
|
||||||
|
retested.
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
The FIPS 203 correction in VaptVupt 5.0.0 changed
|
||||||
|
native
|
||||||
|
.B --pq
|
||||||
|
and
|
||||||
|
.B --pq-only
|
||||||
|
key/archive compatibility relative to releases through 4.2.1. Password and
|
||||||
|
plain archives were not affected by that KEM correction. Consult
|
||||||
|
.B CHANGELOG.md
|
||||||
|
before relying on cross-version recovery, and test restoration before depending
|
||||||
|
on a backup.
|
||||||
|
.
|
||||||
|
.SH LICENSE
|
||||||
|
The ZUPT application and tool code are AGPL-3.0-or-later. The bundled
|
||||||
|
VaptVupt codec source is GPL-3.0-or-later. The two xxHash-derived XXH64 units
|
||||||
|
also carry BSD-2-Clause. The pq-crystals/kyber-derived portions of native
|
||||||
|
ML-KEM also carry CC0-1.0; the x86 BCJ state machine is adapted from
|
||||||
|
public-domain LZMA SDK source. Native X25519 portions adapted from
|
||||||
|
curve25519-donna also carry BSD-3-Clause. Preserve
|
||||||
|
.BR LICENSE ,
|
||||||
|
.BR LICENSE-AGPL-3.0 ,
|
||||||
|
.BR LICENSE-GPL-3.0 ,
|
||||||
|
.BR LICENSE-BSD-2-Clause ,
|
||||||
|
.BR LICENSE-BSD-3-Clause ,
|
||||||
|
.BR LICENSE-CC0-1.0 ,
|
||||||
|
.BR NOTICE ,
|
||||||
|
and
|
||||||
|
.B THIRD-PARTY-NOTICES.md
|
||||||
|
when redistributing the source.
|
||||||
|
Published historical revisions include MIT grants for exact material shipped
|
||||||
|
with those notices; the current license summary does not revoke them. See the
|
||||||
|
5.2.2 licensing erratum in
|
||||||
|
.B CHANGELOG.md
|
||||||
|
for repository evidence.
|
||||||
|
.
|
||||||
|
.SH AUTHORS
|
||||||
|
Cristian Cezar Moisés is the primary upstream author and maintainer. Packaging
|
||||||
|
credits belong in their applicable packaging history and do not imply authorship
|
||||||
|
of the upstream program.
|
||||||
|
.
|
||||||
|
.SH REPORTING BUGS
|
||||||
|
Project issues:
|
||||||
|
.UR https://github.com/cristiancmoises/zupt/issues
|
||||||
|
.UE
|
||||||
|
.
|
||||||
|
.PP
|
||||||
|
Report security vulnerabilities privately as described in
|
||||||
|
.BR SECURITY.md .
|
||||||
|
.
|
||||||
|
.SH SEE ALSO
|
||||||
|
.BR zupt-gui (1)
|
||||||
|
|
@ -3,14 +3,13 @@
|
||||||
|
|
||||||
Copyright (C) 2026 Cristian Cezar Moisés <zupt@riseup.net>
|
Copyright (C) 2026 Cristian Cezar Moisés <zupt@riseup.net>
|
||||||
|
|
||||||
VaptVupt GUI (formerly Zupt GUI; parent application renamed in v3.0.0
|
ZUPT GUI is free
|
||||||
due to a prior INPI Brasil trademark registration of "Zupt") is free
|
|
||||||
software: you can redistribute it and/or modify it under the terms of
|
software: you can redistribute it and/or modify it under the terms of
|
||||||
the GNU Affero General Public License as published by the Free
|
the GNU Affero General Public License as published by the Free
|
||||||
Software Foundation, either version 3 of the License, or (at your
|
Software Foundation, either version 3 of the License, or (at your
|
||||||
option) any later version.
|
option) any later version.
|
||||||
|
|
||||||
VaptVupt GUI is distributed in the hope that it will be useful, but
|
ZUPT GUI is distributed in the hope that it will be useful, but
|
||||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
Affero General Public License for more details.
|
Affero General Public License for more details.
|
||||||
|
|
@ -23,21 +22,26 @@
|
||||||
|
|
||||||
─────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
PRIOR LICENSE NOTE
|
HISTORICAL LICENSE NOTE
|
||||||
|
|
||||||
Earlier copies of this file may have stated "MIT License" — that was
|
Published repository history includes earlier copies of this file
|
||||||
a packaging mistake inherited from a template. The GUI source code's
|
under the MIT License. In particular, commit d4660e6539c8b6eeba81751c018217d978fdd618
|
||||||
SPDX-License-Identifier header has always been AGPL-3.0-or-later;
|
and the v2.2.2 source tag contain an MIT-form gui/LICENSE-GUI. Those
|
||||||
the file-level license here is corrected to match. There is no
|
permissions remain applicable to the exact material distributed under
|
||||||
historical MIT-licensed release of VaptVupt GUI; do not assume MIT
|
them; this file does not revoke or reinterpret a historical grant.
|
||||||
grant from any prior tarball that contained this file.
|
|
||||||
|
The current GUI source carries AGPL-3.0-or-later SPDX notices. Apply
|
||||||
|
the notices shipped with the exact source revision being used. See the
|
||||||
|
5.2.2 licensing erratum in CHANGELOG.md for the repository record.
|
||||||
|
|
||||||
─────────────────────────────────────────────────────────────────────
|
─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
COMMERCIAL LICENSING
|
COMMERCIAL LICENSING
|
||||||
|
|
||||||
The VaptVupt GUI may be commercially relicensed by the author. If
|
The applicable copyright holder may offer controlled first-party
|
||||||
you require relief from copyleft terms (proprietary derivatives,
|
rights under a separately executed commercial agreement. This notice
|
||||||
closed-source bundling, etc.), contact:
|
is not a commercial license grant, does not revoke a public license,
|
||||||
|
and cannot relicense rights that the licensor does not control. For
|
||||||
|
inquiries, contact:
|
||||||
|
|
||||||
sac@securityops.co
|
sac@securityops.co
|
||||||
|
|
|
||||||
228
gui/README.md
228
gui/README.md
|
|
@ -1,123 +1,149 @@
|
||||||
# VaptVupt GUI
|
# ZUPT GUI
|
||||||
|
|
||||||
Desktop application for [vaptvupt](https://git.securityops.co/cristiancmoises/vaptvupt) backup compression with ML-KEM-768 + X25519 post-quantum hybrid encryption.
|
The ZUPT GUI is a Python/Qt front end for the ZUPT 5.2.2 command-line
|
||||||
|
program. It starts the CLI as a subprocess; compression, archive parsing, and
|
||||||
|
cryptography remain in the C program.
|
||||||
|
|
||||||
Works on GNU/Linux, BSD, macOS, and Windows.
|
The canonical project repository is
|
||||||
|
`https://github.com/cristiancmoises/zupt`.
|
||||||
|
|
||||||
## Install
|
## Requirements
|
||||||
|
|
||||||
### Linux (recommended)
|
- a working `zupt` CLI from the same release, available on `PATH` or through
|
||||||
|
the `ZUPT_BIN` environment variable;
|
||||||
|
- Python 3.9 or newer;
|
||||||
|
- PySide6 or PyQt6;
|
||||||
|
- a graphical session for normal use.
|
||||||
|
|
||||||
```bash
|
Install and test the source-only CLI first:
|
||||||
tar xzf vaptvupt-gui.tar.gz && cd vaptvupt-gui
|
|
||||||
./vaptvupt-gui # auto-creates venv, installs PySide6
|
```sh
|
||||||
./install.sh --user # adds right-click menu integration
|
make clean
|
||||||
|
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 1)" \
|
||||||
|
WITH_SDK=0 WITH_PQBOX=0
|
||||||
|
make WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
|
./zupt --version
|
||||||
```
|
```
|
||||||
|
|
||||||
### Windows
|
The GUI detects the capabilities reported by that binary. Native `--pq` and
|
||||||
|
`--pq-only` are available in the default build. SDK and PQBOX controls are
|
||||||
|
usable only when the CLI was built explicitly against the corresponding system
|
||||||
|
development libraries; no precompiled optional library is shipped in Git.
|
||||||
|
|
||||||
**Option A — Installer (recommended):**
|
## Run from the source tree
|
||||||
|
|
||||||
Download `VaptVuptGUI-1.3.0-Setup.exe` and run it. Installs to Program Files, adds Start Menu shortcut, desktop shortcut, right-click context menus, and .zupt file association. Includes uninstaller.
|
Using a virtual environment keeps Python packages outside the repository:
|
||||||
|
|
||||||
**Option B — Build from source:**
|
```sh
|
||||||
|
python3 -m venv ~/.local/share/zupt-gui-venv
|
||||||
```cmd
|
~/.local/share/zupt-gui-venv/bin/pip install PySide6
|
||||||
cd packaging\windows
|
ZUPT_BIN="$PWD/zupt" \
|
||||||
build-windows.bat
|
~/.local/share/zupt-gui-venv/bin/python gui/src/zupt_gui.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Requires Python 3.9+, NSIS 3.x, and a compiled `vaptvupt.exe`.
|
Installing PySide6 can access the Python package index. Do that as an explicit
|
||||||
|
setup step; upstream CLI builds, package builds, and checks do not download
|
||||||
|
dependencies.
|
||||||
|
|
||||||
**Option C — Run directly:**
|
For noninteractive checks:
|
||||||
|
|
||||||
```cmd
|
```sh
|
||||||
pip install PySide6
|
python3 gui/src/zupt_gui.py --version
|
||||||
python src\zupt_gui.py
|
python3 gui/src/zupt_gui.py --selftest
|
||||||
```
|
```
|
||||||
|
|
||||||
### macOS / BSD
|
The first command does not prove that a full desktop session works. Test the
|
||||||
|
actual windows and archive operations on every platform for which a GUI package
|
||||||
|
is published.
|
||||||
|
|
||||||
```bash
|
## Functions
|
||||||
pip3 install PySide6
|
|
||||||
python3 src/zupt_gui.py
|
| Area | Function |
|
||||||
|
|---|---|
|
||||||
|
| Keys | Generate keys supported by the selected CLI build |
|
||||||
|
| Compress | Select input, compression settings, and an available encryption mode |
|
||||||
|
| Extract | Detect archive encryption, request the needed credential, and extract |
|
||||||
|
| Verify | Run archive integrity validation without extraction |
|
||||||
|
| Info | Display metadata reported by the CLI |
|
||||||
|
| Disk | Front end for the CLI disk backup and restore commands |
|
||||||
|
|
||||||
|
Disk operations can require additional operating-system privileges. Run only
|
||||||
|
the specific CLI operation that needs them; do not run the whole desktop session
|
||||||
|
as root.
|
||||||
|
|
||||||
|
## Desktop integration
|
||||||
|
|
||||||
|
`gui/install.sh --user` installs the integration supported by that script for
|
||||||
|
the current user. Review the script and its destination paths before running
|
||||||
|
it. File-manager menus and file associations differ across desktops and
|
||||||
|
operating systems and must be tested on the target system.
|
||||||
|
|
||||||
|
## Packaged GUI builds
|
||||||
|
|
||||||
|
Release pages provide only these GUI artifacts after their separate package and
|
||||||
|
installed off-screen GUI/CLI integration gates pass:
|
||||||
|
|
||||||
|
- `zupt-gui_5.2.2_all.deb`;
|
||||||
|
- `zupt-gui-5.2.2-1.noarch.rpm`;
|
||||||
|
- `zupt-gui-5.2.2-1.src.rpm`;
|
||||||
|
- `zupt-gui-5.2.2-portable.zip`.
|
||||||
|
|
||||||
|
The DEB/RPM packages install the Python/Qt source and depend on the matching
|
||||||
|
`zupt` CLI package. The portable ZIP contains source, launchers, icons, licenses,
|
||||||
|
and provenance only; it bundles no Python, Qt, CLI, or compiled runtime. Its
|
||||||
|
gate scans the assembled and extracted trees, enforces an exact safe-member
|
||||||
|
allowlist, and runs the extracted launcher off-screen against the tested CLI.
|
||||||
|
An absent artifact did not pass its gate and must not be inferred from another
|
||||||
|
format's result.
|
||||||
|
|
||||||
|
GUI AppImage, AppDir and Flatpak bundles, and Windows/macOS GUI installers are
|
||||||
|
not promoted by the upstream 5.2.2 release gates.
|
||||||
|
`packaging/build-gui-appimage.sh` is a downstream-only helper and fails unless
|
||||||
|
its operator supplies the exact verified runtime plus a complete
|
||||||
|
license/source-relink notice through `APPIMAGE_RUNTIME_COMPLIANCE_FILE`; that
|
||||||
|
material is included in the resulting AppDir.
|
||||||
|
|
||||||
|
The downstream Windows GUI helper similarly requires
|
||||||
|
`ZUPT_WINDOWS_RUNTIME_NOTICES_DIR` with a `MANIFEST.txt` that identifies
|
||||||
|
the exact Python, PyInstaller, Qt and PySide/PyQt runtime inputs and their
|
||||||
|
notices. It fails unless the directory also has non-empty
|
||||||
|
`PYTHON-NOTICE.txt`, `PYINSTALLER-NOTICE.txt`, `QT-NOTICE.txt`, and either
|
||||||
|
`PYSIDE6-NOTICE.txt` or `PYQT6-NOTICE.txt`. The installer includes that
|
||||||
|
directory together with every ZUPT license and notice. This requirement does
|
||||||
|
not make the untested GUI installer a 5.2.2 release asset. The promoted Windows
|
||||||
|
ZIP and macOS DMG are CLI-only.
|
||||||
|
|
||||||
|
Packaging recipes and scripts under `gui/packaging/` and `packaging/` are build
|
||||||
|
inputs, not evidence that a package has been accepted by a distribution. They
|
||||||
|
must build the CLI from the immutable source tag with
|
||||||
|
`WITH_SDK=0 WITH_PQBOX=0` unless source-built system dependencies are declared.
|
||||||
|
Generated packages, application bundles, and executables must remain outside
|
||||||
|
Git and outside upstream source archives.
|
||||||
|
|
||||||
|
The former standalone `gui/setup.py` sdist/wheel route is intentionally absent:
|
||||||
|
its outputs did not carry the complete project license payload. Use
|
||||||
|
`gui/install.sh` or the reviewed distribution helpers so the AGPL text and
|
||||||
|
artwork provenance are installed with the GUI.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
Verify the exact interpreter and CLI used by the GUI:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 -c 'import PySide6.QtWidgets'
|
||||||
|
zupt --version
|
||||||
|
ZUPT_BIN=/absolute/path/to/zupt \
|
||||||
|
python3 gui/src/zupt_gui.py --selftest
|
||||||
```
|
```
|
||||||
|
|
||||||
### AppImage (universal Linux)
|
If no window appears, run the GUI from a terminal and check the display/Wayland
|
||||||
|
or X11 error. When reporting a problem, include OS and desktop versions, Python
|
||||||
```bash
|
and Qt binding versions, `zupt --version`, and the non-sensitive error
|
||||||
chmod +x VaptVupt-GUI-1.3.0-x86_64.AppImage
|
message. Never attach passwords, private keys, tokens, or confidential archives.
|
||||||
./VaptVupt-GUI-1.3.0-x86_64.AppImage
|
|
||||||
```
|
|
||||||
|
|
||||||
### Flatpak
|
|
||||||
|
|
||||||
```bash
|
|
||||||
flatpak-builder --install build packaging/flatpak/dev.zupt.gui.yml
|
|
||||||
flatpak run dev.zupt.gui
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
| Tab | Function |
|
|
||||||
|-----|----------|
|
|
||||||
| Keys | Generate ML-KEM-768 + X25519 hybrid keypairs |
|
|
||||||
| Compress | All codecs, levels 1-9, dedup, solid, password, PQ keys |
|
|
||||||
| Extract | Decrypt and extract .zupt archives |
|
|
||||||
| Verify | Check block checksums, view archive metadata |
|
|
||||||
| Disk | Full-disk backup and restore |
|
|
||||||
| About | Version, cryptographic stack, credits |
|
|
||||||
|
|
||||||
All tabs support drag-and-drop. Drop a .zupt file anywhere on the window to extract it. Drop any other file to compress it.
|
|
||||||
|
|
||||||
## System Integration
|
|
||||||
|
|
||||||
### Linux (Nemo / Cinnamon)
|
|
||||||
|
|
||||||
After `./install.sh --user`:
|
|
||||||
- Right-click any file: **Compress with VaptVupt**
|
|
||||||
- Right-click .zupt file: **Extract with VaptVupt**
|
|
||||||
- Double-click .zupt: opens in VaptVupt GUI
|
|
||||||
|
|
||||||
### Windows (after installer)
|
|
||||||
|
|
||||||
- Right-click any file: **Compress with VaptVupt**
|
|
||||||
- Right-click any folder: **Compress with VaptVupt**
|
|
||||||
- Double-click .zupt: opens in VaptVupt GUI
|
|
||||||
- Right-click .zupt: **Verify Integrity**
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
VaptVupt GUI (PySide6, Python)
|
|
||||||
|
|
|
||||||
|-- subprocess.Popen() with streaming stderr
|
|
||||||
|
|
|
||||||
v
|
|
||||||
vaptvupt CLI (Pure C11 binary)
|
|
||||||
ML-KEM-768 + X25519 + AES-256-CTR
|
|
||||||
VaptVupt / LZHP / Store codecs
|
|
||||||
Block deduplication, full-disk backup
|
|
||||||
```
|
|
||||||
|
|
||||||
The GUI calls the vaptvupt CLI binary — all cryptography runs in native C, not Python.
|
|
||||||
|
|
||||||
## Packaging
|
|
||||||
|
|
||||||
| Platform | Format | Tool |
|
|
||||||
|----------|--------|------|
|
|
||||||
| Any | pip | `pip install .` |
|
|
||||||
| Debian/Ubuntu/Mint | .deb | `packaging/deb/control` |
|
|
||||||
| Fedora/RHEL | .rpm | `rpmbuild -ba packaging/rpm/vaptvupt.spec` |
|
|
||||||
| Universal Linux | .AppImage | `packaging/appimage/build-appimage.sh` |
|
|
||||||
| Sandboxed Linux | .flatpak | `packaging/flatpak/dev.zupt.gui.yml` |
|
|
||||||
| Windows | .exe installer | `packaging/windows/build-windows.bat` |
|
|
||||||
| Windows | NSIS .exe | `packaging/windows/zupt-installer.nsi` |
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
- **vaptvupt** v5.2.1 — Cristian Cezar Moisés ([github](https://git.securityops.co/cristiancmoises/vaptvupt))
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
AGPL-3.0-or-later
|
The current GUI source is AGPL-3.0-or-later. Published historical revisions
|
||||||
|
include MIT notices whose grants remain applicable to the exact material
|
||||||
|
distributed under them. See `gui/LICENSE-GUI`, the 5.2.2 licensing erratum in
|
||||||
|
`CHANGELOG.md`, and the repository-level license notices.
|
||||||
|
|
|
||||||
23
gui/assets/README.md
Normal file
23
gui/assets/README.md
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# GUI image assets
|
||||||
|
|
||||||
|
These files are runtime data used by the graphical interface and its packaging;
|
||||||
|
they are not executable code or compiler output.
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `zupt-icon.png` | Main 48 px application icon used by the GUI and packaging. |
|
||||||
|
| `zupt-128.png` | 128 px application icon. |
|
||||||
|
| `zupt.png` | 256 px application/AppDir icon. |
|
||||||
|
| `zupt.ico` | Windows application icon container. |
|
||||||
|
|
||||||
|
Git provenance: all four files were first added by Cristian Cezar Moisés in
|
||||||
|
ZUPT repository commit `d4660e6539c8b6eeba81751c018217d978fdd618`; the repository
|
||||||
|
records no earlier or external origin. Their current Git blobs are byte-for-byte
|
||||||
|
the same blobs present in that commit. That revision distributed them with MIT
|
||||||
|
license notices, whose permissions remain available for those exact files.
|
||||||
|
|
||||||
|
The current GUI tree also carries AGPL-3.0-or-later notices; apply the license
|
||||||
|
option appropriate to the exact material and revision being redistributed and
|
||||||
|
preserve `gui/LICENSE-GUI` and the repository notices. Their historical
|
||||||
|
filenames are retained because build and desktop-integration files refer to
|
||||||
|
them.
|
||||||
200
gui/install.sh
200
gui/install.sh
|
|
@ -1,90 +1,130 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
# Install Zupt GUI + desktop integration
|
|
||||||
# Run: sudo ./install.sh (or ./install.sh --user for per-user install)
|
|
||||||
set -e
|
|
||||||
|
|
||||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
# Install the integrated ZUPT GUI from the checked-out source tree.
|
||||||
USER_INSTALL=0
|
# This script never downloads Python modules or operating-system packages.
|
||||||
[ "$1" = "--user" ] && USER_INSTALL=1
|
|
||||||
|
|
||||||
if [ "$USER_INSTALL" -eq 1 ]; then
|
set -Eeuo pipefail
|
||||||
BIN="$HOME/.local/bin"
|
|
||||||
APPS="$HOME/.local/share/applications"
|
die() {
|
||||||
NEMO="$HOME/.local/share/nemo/actions"
|
printf 'zupt-gui install: %s\n' "$*" >&2
|
||||||
MIME="$HOME/.local/share/mime"
|
exit 1
|
||||||
NAUTILUS="$HOME/.local/share/nautilus/scripts"
|
}
|
||||||
else
|
|
||||||
BIN="/usr/local/bin"
|
usage() {
|
||||||
APPS="/usr/share/applications"
|
cat <<'EOF'
|
||||||
NEMO="/usr/share/nemo/actions"
|
Usage: gui/install.sh [OPTIONS]
|
||||||
MIME="/usr/share/mime"
|
|
||||||
NAUTILUS=""
|
--user install below $HOME/.local
|
||||||
|
--prefix DIR installation prefix (default: /usr/local)
|
||||||
|
--destdir DIR staging root for package builds
|
||||||
|
--legacy-alias install opt-in vaptvupt-gui compatibility symlink
|
||||||
|
-h, --help show this help
|
||||||
|
|
||||||
|
Python 3.9+ and either PySide6 or PyQt6 must already be installed. The
|
||||||
|
zupt CLI must also be installed or selected with ZUPT_BIN at runtime.
|
||||||
|
VAPTVUPT_BIN remains a renamed-era compatibility fallback.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||||
|
repo_root=$(cd -- "$script_dir/.." && pwd -P)
|
||||||
|
prefix=/usr/local
|
||||||
|
destdir=${DESTDIR:-}
|
||||||
|
legacy_alias=0
|
||||||
|
|
||||||
|
while (($#)); do
|
||||||
|
case $1 in
|
||||||
|
--user)
|
||||||
|
[[ -n ${HOME:-} ]] || die 'HOME is unset; cannot use --user'
|
||||||
|
prefix=$HOME/.local
|
||||||
|
;;
|
||||||
|
--prefix)
|
||||||
|
(($# >= 2)) || die '--prefix requires a directory'
|
||||||
|
prefix=$2
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--destdir)
|
||||||
|
(($# >= 2)) || die '--destdir requires a directory'
|
||||||
|
destdir=$2
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--legacy-alias) legacy_alias=1 ;;
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
*) die "unknown option: $1" ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ $prefix == /* ]] || die '--prefix must be an absolute path'
|
||||||
|
[[ -z $destdir || $destdir == /* ]] || die '--destdir must be an absolute path'
|
||||||
|
|
||||||
|
bindir=${BINDIR:-$prefix/bin}
|
||||||
|
libexecdir=${LIBEXECDIR:-$prefix/lib/zupt-gui}
|
||||||
|
datadir=${DATADIR:-$prefix/share}
|
||||||
|
|
||||||
|
for source_file in \
|
||||||
|
"$script_dir/src/zupt_gui.py" \
|
||||||
|
"$script_dir/assets/zupt-icon.png" \
|
||||||
|
"$script_dir/packaging/zupt-gui.desktop" \
|
||||||
|
"$repo_root/doc/zupt-gui.1" \
|
||||||
|
"$repo_root/LICENSE" \
|
||||||
|
"$repo_root/LICENSE-AGPL-3.0" \
|
||||||
|
"$script_dir/LICENSE-GUI" \
|
||||||
|
"$script_dir/assets/README.md"; do
|
||||||
|
[[ -f $source_file ]] || die "required source file is missing: $source_file"
|
||||||
|
done
|
||||||
|
|
||||||
|
stage_bindir=$destdir$bindir
|
||||||
|
stage_libexecdir=$destdir$libexecdir
|
||||||
|
stage_datadir=$destdir$datadir
|
||||||
|
install -d -- "$stage_bindir" "$stage_libexecdir" \
|
||||||
|
"$stage_datadir/applications" \
|
||||||
|
"$stage_datadir/icons/hicolor/256x256/apps" \
|
||||||
|
"$stage_datadir/man/man1" \
|
||||||
|
"$stage_datadir/licenses/zupt-gui"
|
||||||
|
|
||||||
|
install -m 0644 -- "$script_dir/src/zupt_gui.py" "$stage_libexecdir/zupt_gui.py"
|
||||||
|
install -m 0644 -- "$script_dir/packaging/zupt-gui.desktop" \
|
||||||
|
"$stage_datadir/applications/zupt-gui.desktop"
|
||||||
|
install -m 0644 -- "$script_dir/assets/zupt-icon.png" \
|
||||||
|
"$stage_datadir/icons/hicolor/256x256/apps/zupt-gui.png"
|
||||||
|
install -m 0644 -- "$repo_root/doc/zupt-gui.1" \
|
||||||
|
"$stage_datadir/man/man1/zupt-gui.1"
|
||||||
|
install -m 0644 -- "$repo_root/LICENSE" \
|
||||||
|
"$stage_datadir/licenses/zupt-gui/LICENSE"
|
||||||
|
install -m 0644 -- "$repo_root/LICENSE-AGPL-3.0" \
|
||||||
|
"$stage_datadir/licenses/zupt-gui/LICENSE-AGPL-3.0"
|
||||||
|
install -m 0644 -- "$script_dir/LICENSE-GUI" \
|
||||||
|
"$stage_datadir/licenses/zupt-gui/LICENSE-GUI"
|
||||||
|
install -m 0644 -- "$script_dir/assets/README.md" \
|
||||||
|
"$stage_datadir/licenses/zupt-gui/ASSET-PROVENANCE.md"
|
||||||
|
|
||||||
|
# Quote the installed module path for a POSIX shell without embedding DESTDIR.
|
||||||
|
quoted_libexec=${libexecdir//\'/\'\\\'\'}
|
||||||
|
launcher_tmp=$(mktemp "${TMPDIR:-/tmp}/zupt-gui-launcher.XXXXXXXX")
|
||||||
|
trap 'rm -f -- "$launcher_tmp"' EXIT HUP INT TERM
|
||||||
|
cat >"$launcher_tmp" <<EOF
|
||||||
|
#!/bin/sh
|
||||||
|
exec python3 '$quoted_libexec/zupt_gui.py' "\$@"
|
||||||
|
EOF
|
||||||
|
install -m 0755 -- "$launcher_tmp" "$stage_bindir/zupt-gui"
|
||||||
|
|
||||||
|
if ((legacy_alias)); then
|
||||||
|
ln -s -- zupt-gui "$stage_bindir/vaptvupt-gui"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$BIN" "$APPS"
|
if [[ -z $destdir ]]; then
|
||||||
|
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||||
# ── Install launcher ──
|
update-desktop-database "$datadir/applications" >/dev/null 2>&1 || true
|
||||||
cat > "$BIN/zupt-gui" << LAUNCHER
|
fi
|
||||||
#!/bin/bash
|
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
|
||||||
DIR="$DIR"
|
gtk-update-icon-cache -q "$datadir/icons/hicolor" >/dev/null 2>&1 || true
|
||||||
VENV="\$DIR/.venv"
|
|
||||||
PY="\$VENV/bin/python3"
|
|
||||||
[ ! -x "\$PY" ] && python3 -m venv "\$VENV" && "\$VENV/bin/pip" install PySide6 -q
|
|
||||||
exec "\$PY" "\$DIR/src/zupt_gui.py" "\$@"
|
|
||||||
LAUNCHER
|
|
||||||
chmod +x "$BIN/zupt-gui"
|
|
||||||
echo "Installed: $BIN/zupt-gui"
|
|
||||||
|
|
||||||
# ── Desktop entry ──
|
|
||||||
cp "$DIR/packaging/zupt-gui.desktop" "$APPS/"
|
|
||||||
echo "Installed: $APPS/zupt-gui.desktop"
|
|
||||||
|
|
||||||
# ── Nemo actions (Linux Mint / Cinnamon) ──
|
|
||||||
if [ -d "$(dirname "$NEMO")" ] || [ "$USER_INSTALL" -eq 1 ]; then
|
|
||||||
mkdir -p "$NEMO"
|
|
||||||
cp "$DIR/packaging/desktop-integration/nemo/"*.nemo_action "$NEMO/" 2>/dev/null && \
|
|
||||||
echo "Installed: Nemo right-click actions" || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Nautilus scripts (GNOME) ──
|
|
||||||
if [ -n "$NAUTILUS" ]; then
|
|
||||||
mkdir -p "$NAUTILUS"
|
|
||||||
cat > "$NAUTILUS/Compress with Zupt" << 'NSCRIPT'
|
|
||||||
#!/bin/bash
|
|
||||||
zupt-gui --compress $NAUTILUS_SCRIPT_SELECTED_FILE_PATHS
|
|
||||||
NSCRIPT
|
|
||||||
chmod +x "$NAUTILUS/Compress with Zupt"
|
|
||||||
echo "Installed: Nautilus script"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── MIME type for .zupt files ──
|
|
||||||
MIME_XML="$MIME/packages/zupt.xml"
|
|
||||||
if [ ! -f "$MIME_XML" ]; then
|
|
||||||
mkdir -p "$(dirname "$MIME_XML")"
|
|
||||||
cat > "$MIME_XML" << 'MIMEXML'
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
|
||||||
<mime-type type="application/x-zupt">
|
|
||||||
<comment>Zupt Archive</comment>
|
|
||||||
<glob pattern="*.zupt"/>
|
|
||||||
<icon name="package-x-generic"/>
|
|
||||||
</mime-type>
|
|
||||||
</mime-info>
|
|
||||||
MIMEXML
|
|
||||||
if command -v update-mime-database >/dev/null; then
|
|
||||||
update-mime-database "$MIME" 2>/dev/null
|
|
||||||
fi
|
fi
|
||||||
echo "Registered: .zupt MIME type"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Associate .zupt files with zupt-gui ──
|
printf 'Installed zupt-gui below %s%s\n' "$destdir" "$prefix"
|
||||||
if command -v xdg-mime >/dev/null; then
|
if ((!legacy_alias)); then
|
||||||
xdg-mime default zupt-gui.desktop application/x-zupt 2>/dev/null
|
printf 'Legacy vaptvupt-gui alias was not installed (use --legacy-alias to opt in).\n'
|
||||||
echo "Associated: .zupt files open with Zupt GUI"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Done. Right-click any file in your file manager to see Zupt options."
|
|
||||||
echo "Double-click any .zupt file to open it in Zupt GUI."
|
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,7 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
|
||||||
# Build Zupt GUI AppImage
|
|
||||||
# Requires: appimagetool, python3, pip
|
|
||||||
set -e
|
|
||||||
|
|
||||||
APP="zupt-gui"
|
# Compatibility entry point for the canonical source-only GUI builder.
|
||||||
VERSION="1.0.0"
|
set -Eeuo pipefail
|
||||||
APPDIR="${APP}.AppDir"
|
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../../.." && pwd -P)
|
||||||
|
exec "$repo_root/packaging/build-gui-appimage.sh" "$@"
|
||||||
rm -rf "$APPDIR" "${APP}-${VERSION}-x86_64.AppImage"
|
|
||||||
mkdir -p "$APPDIR/usr/bin" "$APPDIR/usr/share/zupt-gui" "$APPDIR/usr/share/applications" "$APPDIR/usr/share/icons/hicolor/256x256/apps"
|
|
||||||
|
|
||||||
# Install Python + deps into AppDir
|
|
||||||
python3 -m venv "$APPDIR/usr/python"
|
|
||||||
"$APPDIR/usr/python/bin/pip" install PySide6 --quiet
|
|
||||||
|
|
||||||
# Copy app
|
|
||||||
cp ../../src/zupt_gui.py "$APPDIR/usr/share/zupt-gui/"
|
|
||||||
cp ../../assets/zupt.png "$APPDIR/usr/share/icons/hicolor/256x256/apps/zupt-gui.png" 2>/dev/null || true
|
|
||||||
|
|
||||||
# Create launcher
|
|
||||||
cat > "$APPDIR/AppRun" << 'APPRUN'
|
|
||||||
#!/bin/bash
|
|
||||||
HERE="$(dirname "$(readlink -f "$0")")"
|
|
||||||
export PATH="$HERE/usr/bin:$HERE/usr/python/bin:$PATH"
|
|
||||||
exec python3 "$HERE/usr/share/zupt-gui/zupt_gui.py" "$@"
|
|
||||||
APPRUN
|
|
||||||
chmod +x "$APPDIR/AppRun"
|
|
||||||
|
|
||||||
# Desktop file
|
|
||||||
cat > "$APPDIR/${APP}.desktop" << DESKTOP
|
|
||||||
[Desktop Entry]
|
|
||||||
Type=Application
|
|
||||||
Name=Zupt GUI
|
|
||||||
Comment=Post-Quantum Backup Utility
|
|
||||||
Exec=zupt-gui
|
|
||||||
Icon=zupt-gui
|
|
||||||
Categories=Utility;Archiving;Security;
|
|
||||||
Terminal=false
|
|
||||||
DESKTOP
|
|
||||||
|
|
||||||
# Build AppImage
|
|
||||||
if command -v appimagetool >/dev/null; then
|
|
||||||
ARCH=x86_64 appimagetool "$APPDIR" "${APP}-${VERSION}-x86_64.AppImage"
|
|
||||||
echo "Built: ${APP}-${VERSION}-x86_64.AppImage"
|
|
||||||
else
|
|
||||||
echo "appimagetool not found. Install from https://github.com/AppImage/AppImageKit"
|
|
||||||
echo "AppDir ready at: $APPDIR/"
|
|
||||||
fi
|
|
||||||
|
|
|
||||||
|
|
@ -1,94 +1,7 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
|
||||||
# Build zupt-gui .deb package
|
|
||||||
set -e
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
|
|
||||||
VERSION="${VERSION:-2.2.1}"
|
# Compatibility entry point for the canonical source-only GUI builder.
|
||||||
ARCH="all"
|
set -Eeuo pipefail
|
||||||
PKG="zupt-gui_${VERSION}_${ARCH}"
|
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd -P)
|
||||||
ROOT="/tmp/$PKG"
|
exec "$repo_root/packaging/build-gui-deb.sh" "$@"
|
||||||
|
|
||||||
rm -rf "$ROOT"
|
|
||||||
mkdir -p "$ROOT/DEBIAN" \
|
|
||||||
"$ROOT/usr/bin" \
|
|
||||||
"$ROOT/usr/lib/python3/dist-packages" \
|
|
||||||
"$ROOT/usr/share/applications" \
|
|
||||||
"$ROOT/usr/share/icons/hicolor/256x256/apps" \
|
|
||||||
"$ROOT/usr/share/doc/zupt-gui"
|
|
||||||
|
|
||||||
# Python module
|
|
||||||
install -m 644 src/zupt_gui.py "$ROOT/usr/lib/python3/dist-packages/zupt_gui.py"
|
|
||||||
|
|
||||||
# Launcher
|
|
||||||
cat > "$ROOT/usr/bin/zupt-gui" <<'LAUNCH'
|
|
||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
sys.path.insert(0, "/usr/lib/python3/dist-packages")
|
|
||||||
from zupt_gui import main
|
|
||||||
sys.exit(main())
|
|
||||||
LAUNCH
|
|
||||||
chmod +x "$ROOT/usr/bin/zupt-gui"
|
|
||||||
|
|
||||||
# Desktop file
|
|
||||||
install -m 644 packaging/zupt-gui.desktop "$ROOT/usr/share/applications/" 2>/dev/null || cat > "$ROOT/usr/share/applications/zupt-gui.desktop" <<'DESK'
|
|
||||||
[Desktop Entry]
|
|
||||||
Name=Zupt GUI
|
|
||||||
GenericName=Post-Quantum Backup Utility
|
|
||||||
Comment=Compress and encrypt files with hybrid PQ crypto
|
|
||||||
Exec=zupt-gui
|
|
||||||
Terminal=false
|
|
||||||
Type=Application
|
|
||||||
Categories=Utility;Archiving;Security;
|
|
||||||
Icon=zupt-gui
|
|
||||||
DESK
|
|
||||||
|
|
||||||
# Icon (use a real one if assets exist)
|
|
||||||
if [ -f assets/zupt-256.png ]; then
|
|
||||||
install -m 644 assets/zupt-256.png "$ROOT/usr/share/icons/hicolor/256x256/apps/zupt-gui.png"
|
|
||||||
elif [ -d ../assets ] && [ -f ../assets/zupt-256.png ]; then
|
|
||||||
install -m 644 ../assets/zupt-256.png "$ROOT/usr/share/icons/hicolor/256x256/apps/zupt-gui.png"
|
|
||||||
else
|
|
||||||
# 1×1 placeholder
|
|
||||||
printf '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\rIDATx\x9cc\xfc\xcf\xc0\x00\x00\x00\x05\x00\x01\xa5\xf6E@\x00\x00\x00\x00IEND\xaeB`\x82' > "$ROOT/usr/share/icons/hicolor/256x256/apps/zupt-gui.png"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Docs
|
|
||||||
install -m 644 README.md "$ROOT/usr/share/doc/zupt-gui/"
|
|
||||||
gzip -9n -c ../CHANGELOG.md > "$ROOT/usr/share/doc/zupt-gui/changelog.gz" 2>/dev/null || true
|
|
||||||
|
|
||||||
cat > "$ROOT/usr/share/doc/zupt-gui/copyright" <<'COPYRIGHT'
|
|
||||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|
||||||
Upstream-Name: zupt-gui
|
|
||||||
Upstream-Contact: Cristian Cezar Moisés <zupt@riseup.net>
|
|
||||||
Source: https://git.securityops.co/cristiancmoises/zupt
|
|
||||||
|
|
||||||
Files: *
|
|
||||||
Copyright: 2025-2026 Cristian Cezar Moisés
|
|
||||||
License: AGPL-3.0+
|
|
||||||
COPYRIGHT
|
|
||||||
|
|
||||||
INSTALLED_SIZE=$(du -sk "$ROOT" | cut -f1)
|
|
||||||
cat > "$ROOT/DEBIAN/control" <<EOF
|
|
||||||
Package: zupt-gui
|
|
||||||
Version: $VERSION
|
|
||||||
Section: utils
|
|
||||||
Priority: optional
|
|
||||||
Architecture: $ARCH
|
|
||||||
Depends: python3 (>= 3.9), python3-pyside6, zupt (>= 2.2.0)
|
|
||||||
Maintainer: Cristian Cezar Moisés <zupt@riseup.net>
|
|
||||||
Installed-Size: $INSTALLED_SIZE
|
|
||||||
Homepage: https://git.securityops.co/cristiancmoises/zupt
|
|
||||||
Description: Zupt GUI — Post-Quantum Backup Utility
|
|
||||||
Cross-platform graphical interface for the zupt backup compression
|
|
||||||
utility with post-quantum hybrid encryption (ML-KEM-768 + X25519).
|
|
||||||
.
|
|
||||||
v2.2+ uses libzuptsdk under the hood for HKDF-SHA3 hybrid combiner,
|
|
||||||
32-byte key commitment, HPKE binding (RFC 9180), and anti-fault
|
|
||||||
double-decapsulation. Supports legacy archives via auto-detection.
|
|
||||||
EOF
|
|
||||||
|
|
||||||
dpkg-deb --build --root-owner-group "$ROOT" "/tmp/$PKG.deb"
|
|
||||||
echo "Built: /tmp/$PKG.deb"
|
|
||||||
dpkg-deb --info "/tmp/$PKG.deb" | head -12
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
Package: zupt-gui
|
Package: zupt-gui
|
||||||
Version: 1.0.0
|
Version: 5.2.2
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: python3 (>= 3.9), python3-pyside6, zupt (>= 2.1.6)
|
Depends: python3 (>= 3.9), python3-pyqt6 | python3-pyside6.qtwidgets, zupt (= 5.2.2)
|
||||||
Maintainer: Cristian Cezar Moises <cristian@zupt.dev>
|
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
|
||||||
Homepage: https://github.com/cristiancmoises/zupt
|
Homepage: https://github.com/cristiancmoises/zupt
|
||||||
Description: Zupt GUI — Post-Quantum Backup Utility
|
Description: Qt graphical interface for the ZUPT backup utility
|
||||||
Cross-platform graphical interface for the zupt backup compression
|
The GUI creates, inspects, verifies, and extracts .zupt archives through the
|
||||||
utility with post-quantum hybrid encryption (ML-KEM-768 + X25519),
|
separately packaged ZUPT command. Native post-quantum modes are available
|
||||||
hardware-adaptive codecs, block-level deduplication, and full-disk
|
in the baseline build; SDK and PQ-box controls follow CLI capability detection.
|
||||||
backup/restore support.
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
[Nemo Action]
|
[Nemo Action]
|
||||||
Name=Compress with Zupt
|
Name=Compress with ZUPT
|
||||||
Comment=Create encrypted .zupt archive
|
Comment=Create a .zupt archive with ZUPT GUI
|
||||||
Exec=zupt-gui --compress %F
|
Exec=zupt-gui --compress %F
|
||||||
Icon-Name=package-x-generic
|
Icon-Name=zupt-gui
|
||||||
Selection=Any
|
Selection=Any
|
||||||
Extensions=any;
|
Extensions=any;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
[Nemo Action]
|
[Nemo Action]
|
||||||
Name=Extract with Zupt
|
Name=Extract with ZUPT
|
||||||
Comment=Decrypt and extract .zupt archive
|
Comment=Extract a .zupt archive with ZUPT GUI
|
||||||
Exec=zupt-gui --extract %F
|
Exec=zupt-gui --extract %F
|
||||||
Icon-Name=package-x-generic
|
Icon-Name=zupt-gui
|
||||||
Selection=S
|
Selection=S
|
||||||
Extensions=zupt;
|
Extensions=zupt;
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,42 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2026 Cristian Cezar Moisés
|
# Copyright (c) 2026 Cristian Cezar Moisés
|
||||||
|
|
||||||
|
# Offline build manifest for the integrated source tree. The Qt/Python base and
|
||||||
|
# SDK are resolved by Flatpak before the build; no command below uses pip or
|
||||||
|
# downloads project dependencies.
|
||||||
app-id: dev.zupt.gui
|
app-id: dev.zupt.gui
|
||||||
runtime: org.freedesktop.Platform
|
runtime: org.kde.Platform
|
||||||
runtime-version: '24.08'
|
runtime-version: '6.8'
|
||||||
sdk: org.freedesktop.Sdk
|
sdk: org.kde.Sdk
|
||||||
|
base: io.qt.PySide.BaseApp
|
||||||
|
base-version: '6.8'
|
||||||
command: zupt-gui
|
command: zupt-gui
|
||||||
|
|
||||||
finish-args:
|
finish-args:
|
||||||
- --share=ipc
|
- --share=ipc
|
||||||
- --socket=x11
|
- --socket=fallback-x11
|
||||||
- --socket=wayland
|
- --socket=wayland
|
||||||
- --filesystem=home
|
- --filesystem=home
|
||||||
- --device=all # For disk backup (block devices)
|
|
||||||
|
|
||||||
modules:
|
modules:
|
||||||
- name: python3-pyside6
|
|
||||||
buildsystem: simple
|
|
||||||
build-commands:
|
|
||||||
- pip3 install --prefix=/app PySide6
|
|
||||||
|
|
||||||
- name: zupt
|
- name: zupt
|
||||||
buildsystem: simple
|
buildsystem: simple
|
||||||
build-commands:
|
build-commands:
|
||||||
- make
|
- make -j${FLATPAK_BUILDER_N_JOBS} WITH_SDK=0 WITH_PQBOX=0
|
||||||
- install -Dm755 zupt /app/bin/zupt
|
- make WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
sources:
|
- make PREFIX=/app WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 install
|
||||||
- type: git
|
- install -Dm644 gui/src/zupt_gui.py /app/bin/zupt_gui.py
|
||||||
url: https://git.securityops.co/cristiancmoises/zupt
|
- install -Dm755 packaging/portable/zupt-gui.sh /app/bin/zupt-gui
|
||||||
tag: v2.1.6
|
- install -Dm644 gui/packaging/zupt-gui.desktop /app/share/applications/dev.zupt.gui.desktop
|
||||||
|
- sed -i 's/^Icon=.*/Icon=dev.zupt.gui/' /app/share/applications/dev.zupt.gui.desktop
|
||||||
- name: zupt-gui
|
- install -Dm644 gui/assets/zupt-icon.png /app/share/icons/hicolor/256x256/apps/dev.zupt.gui.png
|
||||||
buildsystem: simple
|
- install -Dm644 doc/zupt-gui.1 /app/share/man/man1/zupt-gui.1
|
||||||
build-commands:
|
- install -d /app/share/licenses/zupt
|
||||||
- install -Dm755 src/zupt_gui.py /app/bin/zupt-gui
|
- install -m 0644 LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE THIRD-PARTY-NOTICES.md /app/share/licenses/zupt/
|
||||||
- install -Dm644 packaging/zupt-gui.desktop /app/share/applications/dev.zupt.gui.desktop
|
- install -d /app/share/licenses/zupt-gui
|
||||||
|
- install -m 0644 LICENSE-AGPL-3.0 /app/share/licenses/zupt-gui/LICENSE-AGPL-3.0
|
||||||
|
- install -m 0644 gui/LICENSE-GUI /app/share/licenses/zupt-gui/LICENSE-GUI
|
||||||
|
- install -m 0644 gui/assets/README.md /app/share/licenses/zupt-gui/ASSET-PROVENANCE.md
|
||||||
sources:
|
sources:
|
||||||
- type: dir
|
- type: dir
|
||||||
path: .
|
path: ../../..
|
||||||
|
|
|
||||||
|
|
@ -1,92 +1,102 @@
|
||||||
@echo off
|
@echo off
|
||||||
REM ══════════════════════════════════════════════════
|
rem SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
REM Zupt GUI — Windows Build Script
|
rem Build the ZUPT GUI installer without downloading dependencies.
|
||||||
REM Creates: ZuptGUI-2.1.6-Setup.exe
|
rem
|
||||||
REM
|
rem Prerequisites must already be installed: Python 3.9+, PySide6, PyInstaller,
|
||||||
REM Prerequisites:
|
rem Inno Setup 6, and a source-built/tested zupt.exe. The CLI path can be
|
||||||
REM 1. Python 3.9+ (python.org)
|
rem selected with ZUPT_CLI_EXE. Final output goes to ZUPT_DIST_DIR,
|
||||||
REM 2. NSIS 3.x (nsis.sourceforge.io)
|
rem which defaults to a directory below %%TEMP%% (outside the Git checkout).
|
||||||
REM 3. zupt.exe (compiled zupt CLI binary for Windows)
|
rem ZUPT_WINDOWS_RUNTIME_NOTICES_DIR is mandatory and must contain the
|
||||||
REM
|
rem license/notices for the exact Python, PyInstaller, Qt and PySide/PyQt
|
||||||
REM Usage:
|
rem runtime files embedded by this local build.
|
||||||
REM cd packaging\windows
|
|
||||||
REM build-windows.bat
|
|
||||||
REM ══════════════════════════════════════════════════
|
|
||||||
setlocal
|
|
||||||
|
|
||||||
echo.
|
setlocal EnableExtensions
|
||||||
echo Zupt GUI — Windows Build
|
for %%I in ("%~dp0\..\..\..") do set "REPO_ROOT=%%~fI"
|
||||||
echo ════════════════════════
|
set "VERSION=%~1"
|
||||||
echo.
|
if not defined VERSION set "VERSION=5.2.2"
|
||||||
|
if not defined ZUPT_DIST_DIR set "ZUPT_DIST_DIR=%TEMP%\zupt-release"
|
||||||
|
if not defined ZUPT_CLI_EXE set "ZUPT_CLI_EXE=%REPO_ROOT%\zupt.exe"
|
||||||
|
set "WORK=%TEMP%\zupt-gui-build-%RANDOM%-%RANDOM%"
|
||||||
|
set "RC=1"
|
||||||
|
|
||||||
REM ── Step 1: Install Python deps ──
|
where pyinstaller >nul 2>nul || (
|
||||||
echo [1/4] Installing dependencies...
|
echo ERROR: PyInstaller is required and is not downloaded by this script.>&2
|
||||||
pip install PySide6 pyinstaller --quiet --upgrade
|
goto :cleanup
|
||||||
if errorlevel 1 (
|
)
|
||||||
echo ERROR: pip install failed. Is Python in PATH?
|
where ISCC.exe >nul 2>nul || (
|
||||||
pause
|
echo ERROR: Inno Setup 6 ISCC.exe is required.>&2
|
||||||
exit /b 1
|
goto :cleanup
|
||||||
|
)
|
||||||
|
if not exist "%ZUPT_CLI_EXE%" (
|
||||||
|
echo ERROR: source-built CLI not found: %ZUPT_CLI_EXE%>&2
|
||||||
|
goto :cleanup
|
||||||
|
)
|
||||||
|
if not defined ZUPT_WINDOWS_RUNTIME_NOTICES_DIR (
|
||||||
|
echo ERROR: set ZUPT_WINDOWS_RUNTIME_NOTICES_DIR for the exact bundled runtime.>&2
|
||||||
|
goto :cleanup
|
||||||
|
)
|
||||||
|
if not exist "%ZUPT_WINDOWS_RUNTIME_NOTICES_DIR%\MANIFEST.txt" (
|
||||||
|
echo ERROR: runtime notice directory must contain MANIFEST.txt.>&2
|
||||||
|
goto :cleanup
|
||||||
|
)
|
||||||
|
for %%I in ("%ZUPT_WINDOWS_RUNTIME_NOTICES_DIR%\MANIFEST.txt") do if %%~zI LEQ 0 (
|
||||||
|
echo ERROR: runtime notice MANIFEST.txt must not be empty.>&2
|
||||||
|
goto :cleanup
|
||||||
|
)
|
||||||
|
for %%N in (PYTHON-NOTICE.txt PYINSTALLER-NOTICE.txt QT-NOTICE.txt) do (
|
||||||
|
if not exist "%ZUPT_WINDOWS_RUNTIME_NOTICES_DIR%\%%N" (
|
||||||
|
echo ERROR: runtime notice directory is missing %%N.>&2
|
||||||
|
goto :cleanup
|
||||||
|
)
|
||||||
|
for %%I in ("%ZUPT_WINDOWS_RUNTIME_NOTICES_DIR%\%%N") do if %%~zI LEQ 0 (
|
||||||
|
echo ERROR: runtime notice %%N must not be empty.>&2
|
||||||
|
goto :cleanup
|
||||||
|
)
|
||||||
|
)
|
||||||
|
set "QT_BINDING_NOTICE_FOUND="
|
||||||
|
for %%N in (PYSIDE6-NOTICE.txt PYQT6-NOTICE.txt) do if exist "%ZUPT_WINDOWS_RUNTIME_NOTICES_DIR%\%%N" (
|
||||||
|
for %%I in ("%ZUPT_WINDOWS_RUNTIME_NOTICES_DIR%\%%N") do if %%~zI GTR 0 set "QT_BINDING_NOTICE_FOUND=1"
|
||||||
|
)
|
||||||
|
if not defined QT_BINDING_NOTICE_FOUND (
|
||||||
|
echo ERROR: runtime notices need non-empty PYSIDE6-NOTICE.txt or PYQT6-NOTICE.txt.>&2
|
||||||
|
goto :cleanup
|
||||||
)
|
)
|
||||||
|
|
||||||
REM ── Step 2: Build .exe with PyInstaller ──
|
mkdir "%WORK%" || goto :cleanup
|
||||||
echo [2/4] Building ZuptGUI.exe...
|
if not exist "%ZUPT_DIST_DIR%" mkdir "%ZUPT_DIST_DIR%" || goto :cleanup
|
||||||
if exist dist rmdir /s /q dist
|
|
||||||
if exist build rmdir /s /q build
|
|
||||||
|
|
||||||
pyinstaller --onefile --windowed ^
|
"%ZUPT_CLI_EXE%" version >"%WORK%\cli-version.txt" 2>&1 || goto :cleanup
|
||||||
--name "ZuptGUI" ^
|
findstr /b /c:"zupt %VERSION%" "%WORK%\cli-version.txt" >nul || (
|
||||||
--icon "..\..\assets\zupt.ico" ^
|
echo ERROR: CLI version does not match %VERSION%.>&2
|
||||||
--add-data "..\..\assets\zupt.ico;assets" ^
|
goto :cleanup
|
||||||
--add-data "..\..\assets\zupt.png;assets" ^
|
|
||||||
"..\..\src\zupt_gui.py"
|
|
||||||
|
|
||||||
if not exist "dist\ZuptGUI.exe" (
|
|
||||||
echo ERROR: PyInstaller build failed.
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
)
|
||||||
echo Built: dist\ZuptGUI.exe
|
"%ZUPT_CLI_EXE%" help >nul 2>&1 || goto :cleanup
|
||||||
|
|
||||||
REM ── Step 3: Check for zupt.exe ──
|
pyinstaller --noconfirm --clean --onefile --windowed ^
|
||||||
echo [3/4] Checking for zupt.exe...
|
--name zupt-gui ^
|
||||||
if not exist "zupt.exe" (
|
--icon "%REPO_ROOT%\gui\assets\zupt.ico" ^
|
||||||
echo.
|
--add-data "%REPO_ROOT%\gui\assets\zupt.ico;assets" ^
|
||||||
echo WARNING: zupt.exe not found in this directory.
|
--add-data "%REPO_ROOT%\gui\assets\zupt-icon.png;assets" ^
|
||||||
echo The installer needs zupt.exe to bundle the CLI tool.
|
--distpath "%WORK%\dist" ^
|
||||||
echo Options:
|
--workpath "%WORK%\build" ^
|
||||||
echo a) Copy zupt.exe here and re-run this script
|
--specpath "%WORK%" ^
|
||||||
echo b) Build zupt from source with MSYS2/MinGW:
|
"%REPO_ROOT%\gui\src\zupt_gui.py" || goto :cleanup
|
||||||
echo pacman -S mingw-w64-x86_64-gcc make
|
|
||||||
echo cd zupt-2.1.6 ^&^& make
|
|
||||||
echo cp zupt.exe packaging/windows/
|
|
||||||
echo.
|
|
||||||
)
|
|
||||||
|
|
||||||
REM ── Step 4: Build NSIS installer ──
|
set "GUI_EXE=%WORK%\dist\zupt-gui.exe"
|
||||||
echo [4/4] Building installer...
|
if not exist "%GUI_EXE%" goto :cleanup
|
||||||
where makensis >nul 2>&1
|
set "ZUPT_BIN=%ZUPT_CLI_EXE%"
|
||||||
if errorlevel 1 (
|
"%GUI_EXE%" --version >"%WORK%\gui-version.txt" 2>&1 || goto :cleanup
|
||||||
echo.
|
findstr /b /c:"zupt-gui %VERSION%" "%WORK%\gui-version.txt" >nul || goto :cleanup
|
||||||
echo NSIS not found. Install from: https://nsis.sourceforge.io
|
|
||||||
echo Then run: makensis zupt-installer.nsi
|
|
||||||
echo.
|
|
||||||
echo Standalone exe ready at: dist\ZuptGUI.exe
|
|
||||||
pause
|
|
||||||
exit /b 0
|
|
||||||
)
|
|
||||||
|
|
||||||
makensis zupt-installer.nsi
|
ISCC.exe "/DAppVersion=%VERSION%" "/DGuiExecutable=%GUI_EXE%" ^
|
||||||
if errorlevel 1 (
|
"/DCliExecutable=%ZUPT_CLI_EXE%" "/DBuildOutputDir=%ZUPT_DIST_DIR%" ^
|
||||||
echo ERROR: NSIS build failed.
|
"/DRuntimeNoticesDir=%ZUPT_WINDOWS_RUNTIME_NOTICES_DIR%" ^
|
||||||
pause
|
"%REPO_ROOT%\packaging\windows\zupt-gui.iss" || goto :cleanup
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
echo.
|
if not exist "%ZUPT_DIST_DIR%\ZUPT-Setup-%VERSION%.exe" goto :cleanup
|
||||||
echo ════════════════════════════════════════════
|
echo PASS: built %ZUPT_DIST_DIR%\ZUPT-Setup-%VERSION%.exe
|
||||||
echo Build complete!
|
set "RC=0"
|
||||||
echo Standalone: dist\ZuptGUI.exe
|
|
||||||
echo Installer: ZuptGUI-2.1.6-Setup.exe
|
:cleanup
|
||||||
echo ════════════════════════════════════════════
|
if exist "%WORK%" rmdir /s /q "%WORK%"
|
||||||
echo.
|
endlocal & exit /b %RC%
|
||||||
pause
|
|
||||||
|
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
; Zupt GUI — NSIS Installer Script
|
|
||||||
; Builds: ZuptGUI-Setup.exe
|
|
||||||
;
|
|
||||||
; Prerequisites on the build machine:
|
|
||||||
; 1. NSIS 3.x installed (https://nsis.sourceforge.io)
|
|
||||||
; 2. Run build-windows.bat first to create dist/ZuptGUI.exe
|
|
||||||
; 3. Place zupt.exe in this directory
|
|
||||||
; 4. Then: makensis zupt-installer.nsi
|
|
||||||
|
|
||||||
!include "MUI2.nsh"
|
|
||||||
!include "FileFunc.nsh"
|
|
||||||
|
|
||||||
; ── Config ──
|
|
||||||
!define APPNAME "Zupt"
|
|
||||||
!define APPVERSION "2.1.6"
|
|
||||||
!define GUIVERSION "1.0.0"
|
|
||||||
!define PUBLISHER "Cristian Cezar Moises"
|
|
||||||
!define HELPURL "https://github.com/cristiancmoises/zupt"
|
|
||||||
!define EXE "ZuptGUI.exe"
|
|
||||||
!define CLI "zupt.exe"
|
|
||||||
|
|
||||||
Name "${APPNAME} ${APPVERSION}"
|
|
||||||
OutFile "ZuptGUI-${APPVERSION}-Setup.exe"
|
|
||||||
InstallDir "$PROGRAMFILES\${APPNAME}"
|
|
||||||
InstallDirRegKey HKLM "Software\${APPNAME}" "InstallDir"
|
|
||||||
RequestExecutionLevel admin
|
|
||||||
|
|
||||||
; ── UI ──
|
|
||||||
!define MUI_ICON "..\..\assets\zupt.ico"
|
|
||||||
!define MUI_UNICON "..\..\assets\zupt.ico"
|
|
||||||
!define MUI_ABORTWARNING
|
|
||||||
!define MUI_WELCOMEPAGE_TITLE "Install ${APPNAME} ${APPVERSION}"
|
|
||||||
!define MUI_WELCOMEPAGE_TEXT "Post-quantum backup compression with ML-KEM-768 + X25519 hybrid encryption.$\r$\n$\r$\nThis will install the Zupt GUI and CLI tools."
|
|
||||||
|
|
||||||
!insertmacro MUI_PAGE_WELCOME
|
|
||||||
!insertmacro MUI_PAGE_LICENSE "..\..\LICENSE"
|
|
||||||
!insertmacro MUI_PAGE_DIRECTORY
|
|
||||||
!insertmacro MUI_PAGE_INSTFILES
|
|
||||||
!insertmacro MUI_PAGE_FINISH
|
|
||||||
|
|
||||||
!insertmacro MUI_UNPAGE_CONFIRM
|
|
||||||
!insertmacro MUI_UNPAGE_INSTFILES
|
|
||||||
|
|
||||||
!insertmacro MUI_LANGUAGE "English"
|
|
||||||
|
|
||||||
; ── Install ──
|
|
||||||
Section "Install"
|
|
||||||
SetOutPath $INSTDIR
|
|
||||||
|
|
||||||
; Copy files
|
|
||||||
File "dist\${EXE}"
|
|
||||||
File "${CLI}"
|
|
||||||
File "..\..\assets\zupt.ico"
|
|
||||||
File "..\..\LICENSE"
|
|
||||||
File "..\..\README.md"
|
|
||||||
|
|
||||||
; Write uninstaller
|
|
||||||
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
|
||||||
|
|
||||||
; Start Menu
|
|
||||||
CreateDirectory "$SMPROGRAMS\${APPNAME}"
|
|
||||||
CreateShortcut "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" "$INSTDIR\${EXE}" "" "$INSTDIR\zupt.ico"
|
|
||||||
CreateShortcut "$SMPROGRAMS\${APPNAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
|
|
||||||
|
|
||||||
; Desktop shortcut
|
|
||||||
CreateShortcut "$DESKTOP\${APPNAME}.lnk" "$INSTDIR\${EXE}" "" "$INSTDIR\zupt.ico"
|
|
||||||
|
|
||||||
; Add to PATH (so zupt.exe is available system-wide)
|
|
||||||
EnVar::AddValue "PATH" "$INSTDIR"
|
|
||||||
|
|
||||||
; Register .zupt file association
|
|
||||||
WriteRegStr HKCR ".zupt" "" "ZuptArchive"
|
|
||||||
WriteRegStr HKCR "ZuptArchive" "" "Zupt Archive"
|
|
||||||
WriteRegStr HKCR "ZuptArchive\DefaultIcon" "" "$INSTDIR\zupt.ico"
|
|
||||||
WriteRegStr HKCR "ZuptArchive\shell\open\command" "" '"$INSTDIR\${EXE}" --extract "%1"'
|
|
||||||
WriteRegStr HKCR "ZuptArchive\shell\verify\command" "" '"$INSTDIR\${CLI}" test "%1"'
|
|
||||||
WriteRegStr HKCR "ZuptArchive\shell\verify" "" "Verify Integrity"
|
|
||||||
|
|
||||||
; Right-click "Compress with Zupt" on any file
|
|
||||||
WriteRegStr HKCR "*\shell\ZuptCompress" "" "Compress with Zupt"
|
|
||||||
WriteRegStr HKCR "*\shell\ZuptCompress\Icon" "" "$INSTDIR\zupt.ico"
|
|
||||||
WriteRegStr HKCR "*\shell\ZuptCompress\command" "" '"$INSTDIR\${EXE}" --compress "%1"'
|
|
||||||
|
|
||||||
; Right-click on directories
|
|
||||||
WriteRegStr HKCR "Directory\shell\ZuptCompress" "" "Compress with Zupt"
|
|
||||||
WriteRegStr HKCR "Directory\shell\ZuptCompress\Icon" "" "$INSTDIR\zupt.ico"
|
|
||||||
WriteRegStr HKCR "Directory\shell\ZuptCompress\command" "" '"$INSTDIR\${EXE}" --compress "%1"'
|
|
||||||
|
|
||||||
; Add/Remove Programs entry
|
|
||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayName" "${APPNAME} — Post-Quantum Backup"
|
|
||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "UninstallString" "$INSTDIR\Uninstall.exe"
|
|
||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayIcon" "$INSTDIR\zupt.ico"
|
|
||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "Publisher" "${PUBLISHER}"
|
|
||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayVersion" "${APPVERSION}"
|
|
||||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "URLInfoAbout" "${HELPURL}"
|
|
||||||
|
|
||||||
; Calculate installed size
|
|
||||||
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
|
||||||
IntFmt $0 "0x%08X" $0
|
|
||||||
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "EstimatedSize" "$0"
|
|
||||||
SectionEnd
|
|
||||||
|
|
||||||
; ── Uninstall ──
|
|
||||||
Section "Uninstall"
|
|
||||||
; Remove files
|
|
||||||
Delete "$INSTDIR\${EXE}"
|
|
||||||
Delete "$INSTDIR\${CLI}"
|
|
||||||
Delete "$INSTDIR\zupt.ico"
|
|
||||||
Delete "$INSTDIR\LICENSE"
|
|
||||||
Delete "$INSTDIR\README.md"
|
|
||||||
Delete "$INSTDIR\Uninstall.exe"
|
|
||||||
RMDir "$INSTDIR"
|
|
||||||
|
|
||||||
; Remove shortcuts
|
|
||||||
Delete "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk"
|
|
||||||
Delete "$SMPROGRAMS\${APPNAME}\Uninstall.lnk"
|
|
||||||
RMDir "$SMPROGRAMS\${APPNAME}"
|
|
||||||
Delete "$DESKTOP\${APPNAME}.lnk"
|
|
||||||
|
|
||||||
; Remove from PATH
|
|
||||||
EnVar::DeleteValue "PATH" "$INSTDIR"
|
|
||||||
|
|
||||||
; Remove file associations
|
|
||||||
DeleteRegKey HKCR ".zupt"
|
|
||||||
DeleteRegKey HKCR "ZuptArchive"
|
|
||||||
DeleteRegKey HKCR "*\shell\ZuptCompress"
|
|
||||||
DeleteRegKey HKCR "Directory\shell\ZuptCompress"
|
|
||||||
|
|
||||||
; Remove Add/Remove Programs entry
|
|
||||||
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}"
|
|
||||||
DeleteRegKey HKLM "Software\${APPNAME}"
|
|
||||||
SectionEnd
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=Zupt GUI
|
Name=ZUPT GUI
|
||||||
GenericName=Post-Quantum Backup
|
GenericName=Backup and Compression Utility
|
||||||
Comment=Compress, encrypt, and backup with quantum-resistant cryptography
|
Comment=Create, inspect, verify, and extract ZUPT archives
|
||||||
Exec=zupt-gui
|
Exec=zupt-gui %f
|
||||||
Icon=zupt-gui
|
Icon=zupt-gui
|
||||||
Categories=Utility;Archiving;Security;
|
Categories=Utility;Archiving;Compression;
|
||||||
Keywords=backup;compress;encrypt;quantum;zupt;
|
Keywords=backup;archive;compression;encryption;post-quantum;zupt;
|
||||||
Terminal=false
|
Terminal=false
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
MimeType=application/x-zupt;
|
MimeType=application/x-zupt;
|
||||||
|
|
|
||||||
34
gui/setup.py
34
gui/setup.py
|
|
@ -1,34 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
|
||||||
from setuptools import setup, find_packages
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name="zupt-gui",
|
|
||||||
version="1.1.1",
|
|
||||||
description="Zupt GUI — Cross-Platform Post-Quantum Backup Utility",
|
|
||||||
long_description=open("README.md").read(),
|
|
||||||
long_description_content_type="text/markdown",
|
|
||||||
author="Cristian Cezar Moisés",
|
|
||||||
url="https://git.securityops.co/cristiancmoises/zupt",
|
|
||||||
license="AGPL-3.0-or-later",
|
|
||||||
packages=find_packages(where="src"),
|
|
||||||
package_dir={"": "src"},
|
|
||||||
py_modules=["zupt_gui"],
|
|
||||||
python_requires=">=3.9",
|
|
||||||
install_requires=["PySide6>=6.5"],
|
|
||||||
entry_points={
|
|
||||||
"console_scripts": ["zupt-gui=zupt_gui:main"],
|
|
||||||
"gui_scripts": ["zupt-gui=zupt_gui:main"],
|
|
||||||
},
|
|
||||||
classifiers=[
|
|
||||||
"Development Status :: 4 - Beta",
|
|
||||||
"Environment :: X11 Applications :: Qt",
|
|
||||||
"Intended Audience :: End Users/Desktop",
|
|
||||||
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
|
||||||
"Operating System :: OS Independent",
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Topic :: Security :: Cryptography",
|
|
||||||
"Topic :: System :: Archiving :: Compression",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
"""VaptVupt GUI — Cross-Platform Post-Quantum Backup.
|
"""ZUPT GUI — Cross-platform post-quantum backup.
|
||||||
|
|
||||||
Renamed from "Zupt" in v3.0.0 due to INPI Brasil trademark.
|
The original ZUPT product name was restored in 5.2.2. The .zupt archive
|
||||||
The .zupt file extension is preserved.
|
extension, format, codec identifiers, and compatibility remain unchanged.
|
||||||
|
|
||||||
Tries PySide6 first (preferred), falls back to PyQt6 if PySide6 is
|
Tries PySide6 first (preferred), falls back to PyQt6 if PySide6 is
|
||||||
not installed. PyQt6 is the default available package on Debian/Ubuntu
|
not installed. PyQt6 is the default available package on Debian/Ubuntu
|
||||||
|
|
@ -40,32 +40,32 @@ except ImportError:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
if sys.stderr is not None: # None under PyInstaller --windowed
|
if sys.stderr is not None: # None under PyInstaller --windowed
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
"ERROR: vaptvupt-gui requires PySide6 or PyQt6. Install one of:\n"
|
"ERROR: zupt-gui requires PySide6 or PyQt6. Install one of:\n"
|
||||||
" Debian/Ubuntu: sudo apt install python3-pyqt6\n"
|
" Debian/Ubuntu: sudo apt install python3-pyqt6\n"
|
||||||
" Fedora/RHEL: sudo dnf install python3-pyqt6\n"
|
" Fedora/RHEL: sudo dnf install python3-pyqt6\n"
|
||||||
" pip (any OS): pip install PySide6\n"
|
" pip (any OS): pip install PySide6\n"
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# ── Find vaptvupt binary ──
|
# ── Find the ZUPT binary ──
|
||||||
#
|
#
|
||||||
# v3.0.0 rename: the binary is now `vaptvupt`; older installations
|
# ZUPT 5.2.2 restores `zupt` as the primary command. Renamed-era
|
||||||
# (1.x/2.x) ship `zupt`. We try the new name first, fall back to the
|
# installations may still provide `vaptvupt`, so discovery accepts it as a
|
||||||
# old name, and on every candidate verify it's actually executable
|
# compatibility fallback. Every candidate is verified as actually executable
|
||||||
# (not just present). After picking a candidate, we run a quick
|
# (not just present). After picking a candidate, we run a quick
|
||||||
# `version` liveness check — this catches the case where the binary
|
# `version` liveness check — this catches the case where the binary
|
||||||
# exists but can't load its shared library (the original bug report:
|
# exists but can't load its shared library (the original bug report:
|
||||||
# "GUI doesn't find zupt; copying to /usr/local/bin fixes it").
|
# "GUI doesn't find zupt; copying to /usr/local/bin fixes it").
|
||||||
#
|
#
|
||||||
# Diagnostic output goes to stderr so users can `vaptvupt-gui 2>log`
|
# Diagnostic output goes to stderr so users can `zupt-gui 2>log`
|
||||||
# to see exactly which path was tried and why each failed.
|
# to see exactly which path was tried and why each failed.
|
||||||
|
|
||||||
_DISCOVERY_LOG = []
|
_DISCOVERY_LOG = []
|
||||||
|
|
||||||
def _discovery_log(msg):
|
def _discovery_log(msg):
|
||||||
_DISCOVERY_LOG.append(msg)
|
_DISCOVERY_LOG.append(msg)
|
||||||
# Echo to stderr if VAPTVUPT_DEBUG or ZUPT_DEBUG is set
|
# Echo to stderr if ZUPT_DEBUG or its renamed-era alias is set.
|
||||||
if ((os.environ.get("VAPTVUPT_DEBUG") or os.environ.get("ZUPT_DEBUG"))
|
if ((os.environ.get("ZUPT_DEBUG") or os.environ.get("VAPTVUPT_DEBUG"))
|
||||||
and sys.stderr is not None): # None under PyInstaller --windowed
|
and sys.stderr is not None): # None under PyInstaller --windowed
|
||||||
sys.stderr.write(f" [discovery] {msg}\n")
|
sys.stderr.write(f" [discovery] {msg}\n")
|
||||||
|
|
||||||
|
|
@ -91,9 +91,9 @@ def _is_runnable(path):
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
return False, f"OSError: {e}"
|
return False, f"OSError: {e}"
|
||||||
|
|
||||||
def _find_vaptvupt():
|
def _find_zupt():
|
||||||
# 1. Explicit env override
|
# 1. Explicit env override
|
||||||
for env in ("VAPTVUPT_BIN", "ZUPT_BIN"):
|
for env in ("ZUPT_BIN", "VAPTVUPT_BIN"):
|
||||||
p = os.environ.get(env)
|
p = os.environ.get(env)
|
||||||
if p:
|
if p:
|
||||||
ok, reason = _is_runnable(p)
|
ok, reason = _is_runnable(p)
|
||||||
|
|
@ -102,18 +102,18 @@ def _find_vaptvupt():
|
||||||
return p
|
return p
|
||||||
|
|
||||||
# 2. Local project tree (running from a source checkout)
|
# 2. Local project tree (running from a source checkout)
|
||||||
# Try BOTH names (vaptvupt is v3.0.0+, zupt is legacy).
|
# Prefer the canonical name, then the renamed-era compatibility name.
|
||||||
here = Path(getattr(sys, "_MEIPASS", Path(__file__).parent))
|
here = Path(getattr(sys, "_MEIPASS", Path(__file__).parent))
|
||||||
for parent in (here.parent.parent, here.parent, here):
|
for parent in (here.parent.parent, here.parent, here):
|
||||||
for name in ("vaptvupt", "zupt", "vaptvupt.exe", "zupt.exe"):
|
for name in ("zupt", "vaptvupt", "zupt.exe", "vaptvupt.exe"):
|
||||||
c = parent / name
|
c = parent / name
|
||||||
ok, reason = _is_runnable(c)
|
ok, reason = _is_runnable(c)
|
||||||
_discovery_log(f"local {c}: {reason}")
|
_discovery_log(f"local {c}: {reason}")
|
||||||
if ok:
|
if ok:
|
||||||
return str(c.resolve())
|
return str(c.resolve())
|
||||||
|
|
||||||
# 3. System PATH — try new name first, then legacy
|
# 3. System PATH — try the canonical name first, then compatibility.
|
||||||
for name in ("vaptvupt", "zupt"):
|
for name in ("zupt", "vaptvupt"):
|
||||||
found = shutil.which(name)
|
found = shutil.which(name)
|
||||||
if found:
|
if found:
|
||||||
ok, reason = _is_runnable(found)
|
ok, reason = _is_runnable(found)
|
||||||
|
|
@ -127,17 +127,17 @@ def _find_vaptvupt():
|
||||||
# from a desktop session with a minimal PATH that omits /usr/bin"
|
# from a desktop session with a minimal PATH that omits /usr/bin"
|
||||||
# scenario reported against v2.4.8.
|
# scenario reported against v2.4.8.
|
||||||
common = [
|
common = [
|
||||||
# New name (v3.0.0+)
|
# Canonical name
|
||||||
"/usr/local/bin/vaptvupt", "/usr/bin/vaptvupt",
|
|
||||||
"/opt/vaptvupt/bin/vaptvupt", "/opt/homebrew/bin/vaptvupt",
|
|
||||||
# Legacy name (1.x/2.x)
|
|
||||||
"/usr/local/bin/zupt", "/usr/bin/zupt",
|
"/usr/local/bin/zupt", "/usr/bin/zupt",
|
||||||
"/opt/zupt/bin/zupt", "/opt/homebrew/bin/zupt",
|
"/opt/zupt/bin/zupt", "/opt/homebrew/bin/zupt",
|
||||||
|
# Renamed-era compatibility name
|
||||||
|
"/usr/local/bin/vaptvupt", "/usr/bin/vaptvupt",
|
||||||
|
"/opt/vaptvupt/bin/vaptvupt", "/opt/homebrew/bin/vaptvupt",
|
||||||
# Termux (Android) install path
|
# Termux (Android) install path
|
||||||
"/data/data/com.termux/files/usr/bin/vaptvupt",
|
|
||||||
"/data/data/com.termux/files/usr/bin/zupt",
|
"/data/data/com.termux/files/usr/bin/zupt",
|
||||||
|
"/data/data/com.termux/files/usr/bin/vaptvupt",
|
||||||
# Flatpak sandbox runtime path
|
# Flatpak sandbox runtime path
|
||||||
"/app/bin/vaptvupt", "/app/bin/zupt",
|
"/app/bin/zupt", "/app/bin/vaptvupt",
|
||||||
]
|
]
|
||||||
for path in common:
|
for path in common:
|
||||||
ok, reason = _is_runnable(path)
|
ok, reason = _is_runnable(path)
|
||||||
|
|
@ -145,15 +145,13 @@ def _find_vaptvupt():
|
||||||
if ok:
|
if ok:
|
||||||
return path
|
return path
|
||||||
|
|
||||||
# 5. Last resort — return "vaptvupt" and let exec fail loudly later.
|
# 5. Last resort — return "zupt" and let exec fail loudly later.
|
||||||
# A caller-visible error is better than silently returning a path
|
# A caller-visible error is better than silently returning a path
|
||||||
# that doesn't work.
|
# that doesn't work.
|
||||||
_discovery_log("FAILED: no runnable vaptvupt/zupt binary found")
|
_discovery_log("FAILED: no runnable zupt/vaptvupt binary found")
|
||||||
return "vaptvupt"
|
return "zupt"
|
||||||
|
|
||||||
# Backward-compat: code elsewhere in this file still uses `ZUPT`.
|
ZUPT_CLI = _find_zupt()
|
||||||
VAPTVUPT = _find_vaptvupt()
|
|
||||||
ZUPT = VAPTVUPT # legacy alias used throughout the rest of zupt_gui.py
|
|
||||||
|
|
||||||
# ── Query version ONCE at import (cached) ──
|
# ── Query version ONCE at import (cached) ──
|
||||||
#
|
#
|
||||||
|
|
@ -174,11 +172,11 @@ ZUPT = VAPTVUPT # legacy alias used throughout the rest of zupt_gui.py
|
||||||
_VERSION_RE = re.compile(r'^(?:vaptvupt|zupt)\s+(\d+\.\d+\.\d+(?:[._A-Za-z0-9-]*)?)')
|
_VERSION_RE = re.compile(r'^(?:vaptvupt|zupt)\s+(\d+\.\d+\.\d+(?:[._A-Za-z0-9-]*)?)')
|
||||||
|
|
||||||
def _get_version():
|
def _get_version():
|
||||||
short = "vaptvupt (not found)"
|
short = "zupt (not found)"
|
||||||
number = "?"
|
number = "?"
|
||||||
full = ""
|
full = ""
|
||||||
try:
|
try:
|
||||||
r = subprocess.run([VAPTVUPT, "version"], capture_output=True, stdin=subprocess.DEVNULL, text=True, timeout=5)
|
r = subprocess.run([ZUPT_CLI, "version"], capture_output=True, stdin=subprocess.DEVNULL, text=True, timeout=5)
|
||||||
if r.returncode == 0:
|
if r.returncode == 0:
|
||||||
full = r.stdout.strip()
|
full = r.stdout.strip()
|
||||||
lines = full.split("\n")
|
lines = full.split("\n")
|
||||||
|
|
@ -194,24 +192,30 @@ ZUPT_VER_SHORT, ZUPT_VER_NUMBER, ZUPT_VER_FULL = _get_version()
|
||||||
|
|
||||||
# ── Detect build capabilities from `version` (and `help` as fallback) ──
|
# ── Detect build capabilities from `version` (and `help` as fallback) ──
|
||||||
#
|
#
|
||||||
# The default build is SOURCE-ONLY: the libvuptsdk-backed modes (Argon2id
|
# The default build is SOURCE-ONLY: the system-lib-backed modes (Argon2id,
|
||||||
# KDF, --pq-sdk, --pq-box) are absent and fail with exit 1. Offering them in
|
# --pq-sdk and --pq-box) are absent and fail with exit 1. Offering them in
|
||||||
# the UI is the #1 reason "functions don't work". We detect what THIS binary
|
# the UI is the #1 reason "functions don't work". We detect what THIS binary
|
||||||
# actually supports and build the encryption UI around it:
|
# actually supports and build the encryption UI around it:
|
||||||
# - SDK_AVAILABLE : --pq-sdk / --pq-box / Argon2id compiled in (WITH_SDK=1)
|
# - SDK_AVAILABLE : --pq-sdk / Argon2id compiled in (WITH_SDK=1)
|
||||||
|
# - PQBOX_AVAILABLE: --pq-box compiled in (WITH_PQBOX=1)
|
||||||
# - PQONLY_AVAILABLE: native --pq-only (full post-quantum, v4.2.0+)
|
# - PQONLY_AVAILABLE: native --pq-only (full post-quantum, v4.2.0+)
|
||||||
# - DEFAULT_KDF : the password KDF this build actually uses
|
# - DEFAULT_KDF : the password KDF this build actually uses
|
||||||
# The `version` banner carries a machine-readable "Build:" line (v4.2.1+);
|
# The `version` banner carries a machine-readable "Build integrations:" line;
|
||||||
# for older binaries we fall back to `help` text and default SDK off (safe:
|
# for older binaries we fall back to `help` text and default SDK off (safe:
|
||||||
# the native --pq / --pq-only / password modes work on every build).
|
# the native --pq / --pq-only / password modes work on every build).
|
||||||
def _get_caps():
|
def _get_caps():
|
||||||
sdk = False
|
sdk = False
|
||||||
|
pqbox = False
|
||||||
pqonly = False
|
pqonly = False
|
||||||
default_kdf = "PBKDF2-SHA256"
|
default_kdf = "PBKDF2-SHA256"
|
||||||
blob = ZUPT_VER_FULL or ""
|
blob = ZUPT_VER_FULL or ""
|
||||||
for line in blob.splitlines():
|
for line in blob.splitlines():
|
||||||
low = line.lower()
|
low = line.lower()
|
||||||
if low.startswith("build:"):
|
if low.startswith("build integrations:"):
|
||||||
|
sdk = "libvuptsdk=enabled" in low
|
||||||
|
pqbox = "libpqvaptvupt=enabled" in low
|
||||||
|
elif low.startswith("build:"):
|
||||||
|
# Compatibility with the pre-5.2.2 combined build banner.
|
||||||
sdk = ("full" in low) and ("vuptsdk" in low)
|
sdk = ("full" in low) and ("vuptsdk" in low)
|
||||||
elif low.startswith("kdf:"):
|
elif low.startswith("kdf:"):
|
||||||
default_kdf = "Argon2id" if "argon2id (default)" in low else "PBKDF2-SHA256"
|
default_kdf = "Argon2id" if "argon2id (default)" in low else "PBKDF2-SHA256"
|
||||||
|
|
@ -219,15 +223,15 @@ def _get_caps():
|
||||||
pqonly = True
|
pqonly = True
|
||||||
if not pqonly:
|
if not pqonly:
|
||||||
try:
|
try:
|
||||||
h = subprocess.run([VAPTVUPT, "help"], capture_output=True, stdin=subprocess.DEVNULL, text=True, timeout=5)
|
h = subprocess.run([ZUPT_CLI, "help"], capture_output=True, stdin=subprocess.DEVNULL, text=True, timeout=5)
|
||||||
txt = (h.stdout or "") + (h.stderr or "")
|
txt = (h.stdout or "") + (h.stderr or "")
|
||||||
if "--pq-only" in txt:
|
if "--pq-only" in txt:
|
||||||
pqonly = True
|
pqonly = True
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return sdk, pqonly, default_kdf
|
return sdk, pqbox, pqonly, default_kdf
|
||||||
|
|
||||||
SDK_AVAILABLE, PQONLY_AVAILABLE, DEFAULT_KDF = _get_caps()
|
SDK_AVAILABLE, PQBOX_AVAILABLE, PQONLY_AVAILABLE, DEFAULT_KDF = _get_caps()
|
||||||
|
|
||||||
# Post-quantum recipient modes offered in the UI, keyed to CLI flags.
|
# Post-quantum recipient modes offered in the UI, keyed to CLI flags.
|
||||||
# token -> (label, keygen-flag-list, compress/extract-flag)
|
# token -> (label, keygen-flag-list, compress/extract-flag)
|
||||||
|
|
@ -242,6 +246,8 @@ def pq_mode_options(include_auto=False):
|
||||||
opts.append(("Full PQ — ML-KEM-768 only", "pqonly"))
|
opts.append(("Full PQ — ML-KEM-768 only", "pqonly"))
|
||||||
if SDK_AVAILABLE:
|
if SDK_AVAILABLE:
|
||||||
opts.append(("SDK v2 — HKDF + commitment + HPKE", "sdk"))
|
opts.append(("SDK v2 — HKDF + commitment + HPKE", "sdk"))
|
||||||
|
if PQBOX_AVAILABLE:
|
||||||
|
opts.append(("PQ sealed box — system libpqvaptvupt", "box"))
|
||||||
return opts
|
return opts
|
||||||
|
|
||||||
# token -> (extra keygen flags, encrypt/decrypt flag)
|
# token -> (extra keygen flags, encrypt/decrypt flag)
|
||||||
|
|
@ -249,12 +255,13 @@ _PQ_FLAG = {
|
||||||
"pq": ([], "--pq"),
|
"pq": ([], "--pq"),
|
||||||
"pqonly": (["--pq-only"], "--pq-only"),
|
"pqonly": (["--pq-only"], "--pq-only"),
|
||||||
"sdk": (["--sdk"], "--pq-sdk"),
|
"sdk": (["--sdk"], "--pq-sdk"),
|
||||||
|
"box": (["--box"], "--pq-box"),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _archive_info_text(archive):
|
def _archive_info_text(archive):
|
||||||
"""Return the `info` output for an archive (no password/key needed), or ""."""
|
"""Return the `info` output for an archive (no password/key needed), or ""."""
|
||||||
try:
|
try:
|
||||||
r = subprocess.run([VAPTVUPT, "info", archive], capture_output=True,
|
r = subprocess.run([ZUPT_CLI, "info", archive], capture_output=True,
|
||||||
stdin=subprocess.DEVNULL, text=True, timeout=15)
|
stdin=subprocess.DEVNULL, text=True, timeout=15)
|
||||||
return (r.stdout or "") + (r.stderr or "")
|
return (r.stdout or "") + (r.stderr or "")
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -263,6 +270,8 @@ def _archive_info_text(archive):
|
||||||
def _detect_archive_pq(archive):
|
def _detect_archive_pq(archive):
|
||||||
"""Inspect an archive's `info` and return the matching PQ token, or None."""
|
"""Inspect an archive's `info` and return the matching PQ token, or None."""
|
||||||
low = _archive_info_text(archive).lower()
|
low = _archive_info_text(archive).lower()
|
||||||
|
if "pq box" in low or "pq-box" in low or "sealed box" in low or "sealed-box" in low:
|
||||||
|
return "box"
|
||||||
if "ml-kem-768 only" in low or "no classical" in low:
|
if "ml-kem-768 only" in low or "no classical" in low:
|
||||||
return "pqonly"
|
return "pqonly"
|
||||||
if "sdk v2" in low or "hpke" in low:
|
if "sdk v2" in low or "hpke" in low:
|
||||||
|
|
@ -274,7 +283,7 @@ def _detect_archive_pq(archive):
|
||||||
def _detect_archive_enc(archive):
|
def _detect_archive_enc(archive):
|
||||||
"""Detect how an archive is protected, reading only its header (`info`, no
|
"""Detect how an archive is protected, reading only its header (`info`, no
|
||||||
credential). Returns (kind, human_label):
|
credential). Returns (kind, human_label):
|
||||||
kind: "none" | "password" | "pq" | "pqonly" | "sdk" | "unknown"
|
kind: "none" | "password" | "pq" | "pqonly" | "sdk" | "box" | "unknown"
|
||||||
Used to guide the user (which credential to supply) and to pick the right
|
Used to guide the user (which credential to supply) and to pick the right
|
||||||
decrypt flag automatically instead of relying on a mode dropdown."""
|
decrypt flag automatically instead of relying on a mode dropdown."""
|
||||||
txt = _archive_info_text(archive)
|
txt = _archive_info_text(archive)
|
||||||
|
|
@ -289,6 +298,8 @@ def _detect_archive_enc(archive):
|
||||||
break
|
break
|
||||||
if encrypted is False:
|
if encrypted is False:
|
||||||
return "none", "not encrypted"
|
return "none", "not encrypted"
|
||||||
|
if "pq box" in low or "pq-box" in low or "sealed box" in low or "sealed-box" in low:
|
||||||
|
return "box", "PQ sealed box (system libpqvaptvupt)"
|
||||||
if "ml-kem-768 only" in low or "no classical" in low:
|
if "ml-kem-768 only" in low or "no classical" in low:
|
||||||
return "pqonly", "full post-quantum (ML-KEM-768)"
|
return "pqonly", "full post-quantum (ML-KEM-768)"
|
||||||
if "sdk v2" in low or "hpke" in low:
|
if "sdk v2" in low or "hpke" in low:
|
||||||
|
|
@ -362,10 +373,10 @@ QFrame#sep { background: #1a2a30; max-height: 1px; }
|
||||||
|
|
||||||
def run_zupt(args, timeout=30):
|
def run_zupt(args, timeout=30):
|
||||||
try:
|
try:
|
||||||
r = subprocess.run([ZUPT]+list(args), capture_output=True, text=True,
|
r = subprocess.run([ZUPT_CLI]+list(args), capture_output=True, text=True,
|
||||||
stdin=subprocess.DEVNULL, timeout=timeout)
|
stdin=subprocess.DEVNULL, timeout=timeout)
|
||||||
return r.returncode, r.stdout, r.stderr
|
return r.returncode, r.stdout, r.stderr
|
||||||
except FileNotFoundError: return -1, "", f"vaptvupt not found: {VAPTVUPT}\n\nDiscovery log:\n" + "\n".join(_DISCOVERY_LOG[-10:])
|
except FileNotFoundError: return -1, "", f"zupt not found: {ZUPT_CLI}\n\nDiscovery log:\n" + "\n".join(_DISCOVERY_LOG[-10:])
|
||||||
except subprocess.TimeoutExpired: return -1, "", "Timed out"
|
except subprocess.TimeoutExpired: return -1, "", "Timed out"
|
||||||
|
|
||||||
class Worker(QObject):
|
class Worker(QObject):
|
||||||
|
|
@ -382,7 +393,7 @@ class Worker(QObject):
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
super().__init__(); self.args = args; self.proc = None; self._cancelled = False
|
super().__init__(); self.args = args; self.proc = None; self._cancelled = False
|
||||||
def run(self):
|
def run(self):
|
||||||
self.log.emit(f"$ {Path(VAPTVUPT).name} {' '.join(self.args)}")
|
self.log.emit(f"$ {Path(ZUPT_CLI).name} {' '.join(self.args)}")
|
||||||
try:
|
try:
|
||||||
# stdin=DEVNULL: the CLI prompts on a terminal for some inputs
|
# stdin=DEVNULL: the CLI prompts on a terminal for some inputs
|
||||||
# (e.g. bare -p); a child that reads stdin inherited from the GUI's
|
# (e.g. bare -p); a child that reads stdin inherited from the GUI's
|
||||||
|
|
@ -391,7 +402,7 @@ class Worker(QObject):
|
||||||
# (the CLI's human output is on stderr; stdout is empty) — no
|
# (the CLI's human output is on stderr; stdout is empty) — no
|
||||||
# second pipe that could fill while we drain the first.
|
# second pipe that could fill while we drain the first.
|
||||||
self.proc = proc = subprocess.Popen(
|
self.proc = proc = subprocess.Popen(
|
||||||
[ZUPT]+self.args, stdout=subprocess.PIPE,
|
[ZUPT_CLI]+self.args, stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL)
|
stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL)
|
||||||
if self._cancelled: # cancel() ran before Popen finished (see below)
|
if self._cancelled: # cancel() ran before Popen finished (see below)
|
||||||
proc.kill()
|
proc.kill()
|
||||||
|
|
@ -424,7 +435,7 @@ class Worker(QObject):
|
||||||
if buf.strip() and not self._PCT_RE.search(buf):
|
if buf.strip() and not self._PCT_RE.search(buf):
|
||||||
lines.append(buf); self.log.emit(buf)
|
lines.append(buf); self.log.emit(buf)
|
||||||
self.done.emit(proc.returncode, "", "\n".join(lines))
|
self.done.emit(proc.returncode, "", "\n".join(lines))
|
||||||
except FileNotFoundError: self.done.emit(-1, "", f"vaptvupt not found: {VAPTVUPT}")
|
except FileNotFoundError: self.done.emit(-1, "", f"zupt not found: {ZUPT_CLI}")
|
||||||
except subprocess.TimeoutExpired: proc.kill(); self.done.emit(-1, "", "Timed out")
|
except subprocess.TimeoutExpired: proc.kill(); self.done.emit(-1, "", "Timed out")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
# Any escape from this slot would strand the job forever (done never
|
# Any escape from this slot would strand the job forever (done never
|
||||||
|
|
@ -611,10 +622,10 @@ class KeysTab(QWidget):
|
||||||
v.addWidget(QLabel("Writes a private key and its matching public key."))
|
v.addWidget(QLabel("Writes a private key and its matching public key."))
|
||||||
|
|
||||||
v.addWidget(H("Private key output"))
|
v.addWidget(H("Private key output"))
|
||||||
self.gen_priv = PathField("e.g. ~/vaptvupt_private.key", "save", "Key (*.key);;All (*)")
|
self.gen_priv = PathField("e.g. ~/zupt_private.key", "save", "Key (*.key);;All (*)")
|
||||||
v.addWidget(self.gen_priv)
|
v.addWidget(self.gen_priv)
|
||||||
v.addWidget(H("Public key output"))
|
v.addWidget(H("Public key output"))
|
||||||
self.gen_pub = PathField("e.g. ~/vaptvupt_public.key", "save", "Key (*.key);;All (*)")
|
self.gen_pub = PathField("e.g. ~/zupt_public.key", "save", "Key (*.key);;All (*)")
|
||||||
v.addWidget(self.gen_pub)
|
v.addWidget(self.gen_pub)
|
||||||
|
|
||||||
self.gen_btn = QPushButton("Generate Keypair")
|
self.gen_btn = QPushButton("Generate Keypair")
|
||||||
|
|
@ -634,7 +645,7 @@ class KeysTab(QWidget):
|
||||||
v.addWidget(self.exp_priv)
|
v.addWidget(self.exp_priv)
|
||||||
|
|
||||||
v.addWidget(H("Public key output"))
|
v.addWidget(H("Public key output"))
|
||||||
self.exp_pub = PathField("e.g. ~/vaptvupt_public.key", "save", "Key (*.key);;All (*)")
|
self.exp_pub = PathField("e.g. ~/zupt_public.key", "save", "Key (*.key);;All (*)")
|
||||||
v.addWidget(self.exp_pub)
|
v.addWidget(self.exp_pub)
|
||||||
|
|
||||||
self.exp_btn = QPushButton("Export Public Key")
|
self.exp_btn = QPushButton("Export Public Key")
|
||||||
|
|
@ -653,7 +664,7 @@ class KeysTab(QWidget):
|
||||||
return (priv.rsplit(".", 1)[0] + "_public.key") if "." in priv else priv + ".pub"
|
return (priv.rsplit(".", 1)[0] + "_public.key") if "." in priv else priv + ".pub"
|
||||||
|
|
||||||
def _generate(self):
|
def _generate(self):
|
||||||
p = self.gen_priv.path() or str(Path.home() / "vaptvupt_private.key")
|
p = self.gen_priv.path() or str(Path.home() / "zupt_private.key")
|
||||||
self.gen_priv.edit.setText(p)
|
self.gen_priv.edit.setText(p)
|
||||||
pub = self.gen_pub.path() or self._default_pub(p)
|
pub = self.gen_pub.path() or self._default_pub(p)
|
||||||
self.gen_pub.edit.setText(pub)
|
self.gen_pub.edit.setText(pub)
|
||||||
|
|
@ -679,7 +690,7 @@ class KeysTab(QWidget):
|
||||||
def _export(self):
|
def _export(self):
|
||||||
priv = self.exp_priv.path()
|
priv = self.exp_priv.path()
|
||||||
pub = self.exp_pub.path()
|
pub = self.exp_pub.path()
|
||||||
if not priv: QMessageBox.warning(self, "VaptVupt", "Select the private key file."); return
|
if not priv: QMessageBox.warning(self, "ZUPT", "Select the private key file."); return
|
||||||
if not pub:
|
if not pub:
|
||||||
pub = self._default_pub(priv); self.exp_pub.edit.setText(pub)
|
pub = self._default_pub(priv); self.exp_pub.edit.setText(pub)
|
||||||
tok = self._token()
|
tok = self._token()
|
||||||
|
|
@ -701,7 +712,7 @@ class CompressTab(QWidget):
|
||||||
v.addWidget(H("Source files / directory"))
|
v.addWidget(H("Source files / directory"))
|
||||||
self.src = PathField("Drop files here or browse", "multi"); v.addWidget(self.src)
|
self.src = PathField("Drop files here or browse", "multi"); v.addWidget(self.src)
|
||||||
v.addWidget(H("Output archive"))
|
v.addWidget(H("Output archive"))
|
||||||
self.dst = PathField("e.g. backup.zupt", "save", "VaptVupt archive (*.zupt);;All (*)"); v.addWidget(self.dst)
|
self.dst = PathField("e.g. backup.zupt", "save", "ZUPT archive (*.zupt);;All (*)"); v.addWidget(self.dst)
|
||||||
row = QHBoxLayout(); row.setSpacing(16)
|
row = QHBoxLayout(); row.setSpacing(16)
|
||||||
for label, widget in [("Codec", self._mk_codec()), ("Level", self._mk_level())]:
|
for label, widget in [("Codec", self._mk_codec()), ("Level", self._mk_level())]:
|
||||||
c = QVBoxLayout(); c.addWidget(H(label)); c.addWidget(widget); row.addLayout(c)
|
c = QVBoxLayout(); c.addWidget(H(label)); c.addWidget(widget); row.addLayout(c)
|
||||||
|
|
@ -735,7 +746,7 @@ class CompressTab(QWidget):
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
srcs = self.src.paths()
|
srcs = self.src.paths()
|
||||||
if not srcs or not srcs[0]: QMessageBox.warning(self, "VaptVupt", "Select files."); return
|
if not srcs or not srcs[0]: QMessageBox.warning(self, "ZUPT", "Select files."); return
|
||||||
dst = self.dst.path() or srcs[0] + ".zupt"; self.dst.edit.setText(dst)
|
dst = self.dst.path() or srcs[0] + ".zupt"; self.dst.edit.setText(dst)
|
||||||
cmd = ["compress", "-l", str(self.level.value())]
|
cmd = ["compress", "-l", str(self.level.value())]
|
||||||
cm = {"AUTO": None, "VaptVupt": "--vv", "LZHP": "--lzhp", "Store": "-s"}
|
cm = {"AUTO": None, "VaptVupt": "--vv", "LZHP": "--lzhp", "Store": "-s"}
|
||||||
|
|
@ -758,7 +769,7 @@ class ExtractTab(QWidget):
|
||||||
v = QVBoxLayout(inner); v.setContentsMargins(24,24,24,24); v.setSpacing(10)
|
v = QVBoxLayout(inner); v.setContentsMargins(24,24,24,24); v.setSpacing(10)
|
||||||
v.addWidget(QLabel("Extract and decrypt a .zupt archive."))
|
v.addWidget(QLabel("Extract and decrypt a .zupt archive."))
|
||||||
v.addWidget(Sep())
|
v.addWidget(Sep())
|
||||||
v.addWidget(H("Archive")); self.arc = PathField("Drop .zupt here", filters="VaptVupt archive (*.zupt);;All (*)"); v.addWidget(self.arc)
|
v.addWidget(H("Archive")); self.arc = PathField("Drop .zupt here", filters="ZUPT archive (*.zupt);;All (*)"); v.addWidget(self.arc)
|
||||||
v.addWidget(H("Output directory")); self.out = PathField("Same as archive", "dir"); v.addWidget(self.out)
|
v.addWidget(H("Output directory")); self.out = PathField("Same as archive", "dir"); v.addWidget(self.out)
|
||||||
enc = QHBoxLayout(); enc.setSpacing(16)
|
enc = QHBoxLayout(); enc.setSpacing(16)
|
||||||
pw = QVBoxLayout(); pw.addWidget(H("Password")); self.pw = PwField(); pw.addWidget(self.pw); enc.addLayout(pw)
|
pw = QVBoxLayout(); pw.addWidget(H("Password")); self.pw = PwField(); pw.addWidget(self.pw); enc.addLayout(pw)
|
||||||
|
|
@ -768,7 +779,7 @@ class ExtractTab(QWidget):
|
||||||
self._pqmodes = pq_mode_options(include_auto=True)
|
self._pqmodes = pq_mode_options(include_auto=True)
|
||||||
for label, _tok in self._pqmodes:
|
for label, _tok in self._pqmodes:
|
||||||
self.pqmode.addItem(label)
|
self.pqmode.addItem(label)
|
||||||
self.pqmode.setToolTip("Auto-detect reads the archive header (vaptvupt info) to pick the\n"
|
self.pqmode.setToolTip("Auto-detect reads the archive header (zupt info) to pick the\n"
|
||||||
"right mode. Or choose it explicitly to match your private key.")
|
"right mode. Or choose it explicitly to match your private key.")
|
||||||
mode_box.addWidget(self.pqmode); mode_box.addStretch(); enc.addLayout(mode_box)
|
mode_box.addWidget(self.pqmode); mode_box.addStretch(); enc.addLayout(mode_box)
|
||||||
v.addLayout(enc)
|
v.addLayout(enc)
|
||||||
|
|
@ -780,7 +791,7 @@ class ExtractTab(QWidget):
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
arc = self.arc.path()
|
arc = self.arc.path()
|
||||||
if not arc: QMessageBox.warning(self, "VaptVupt", "Select an archive."); return
|
if not arc: QMessageBox.warning(self, "ZUPT", "Select an archive."); return
|
||||||
if not os.path.isfile(arc):
|
if not os.path.isfile(arc):
|
||||||
self.log.clear(); self.log.append(f"No such file: {arc}"); return
|
self.log.clear(); self.log.append(f"No such file: {arc}"); return
|
||||||
# Read the header (no credential) so we can guide the user instead of
|
# Read the header (no credential) so we can guide the user instead of
|
||||||
|
|
@ -791,7 +802,7 @@ class ExtractTab(QWidget):
|
||||||
self.log.append("This archive is password-encrypted.\n"
|
self.log.append("This archive is password-encrypted.\n"
|
||||||
"Enter the password above, then click Extract again.")
|
"Enter the password above, then click Extract again.")
|
||||||
return
|
return
|
||||||
if kind in ("pq", "pqonly", "sdk") and not self.pq.path():
|
if kind in ("pq", "pqonly", "sdk", "box") and not self.pq.path():
|
||||||
self.log.clear()
|
self.log.clear()
|
||||||
self.log.append(f"This archive uses {label} encryption.\n"
|
self.log.append(f"This archive uses {label} encryption.\n"
|
||||||
"Select the matching private key above, then click Extract again.")
|
"Select the matching private key above, then click Extract again.")
|
||||||
|
|
@ -803,7 +814,7 @@ class ExtractTab(QWidget):
|
||||||
if self.pq.path():
|
if self.pq.path():
|
||||||
# Prefer the header-detected mode; fall back to the dropdown for an
|
# Prefer the header-detected mode; fall back to the dropdown for an
|
||||||
# unreadable header. Auto-detect can't pick the wrong flag this way.
|
# unreadable header. Auto-detect can't pick the wrong flag this way.
|
||||||
tok = kind if kind in ("pq", "pqonly", "sdk") else self._pqmodes[self.pqmode.currentIndex()][1]
|
tok = kind if kind in ("pq", "pqonly", "sdk", "box") else self._pqmodes[self.pqmode.currentIndex()][1]
|
||||||
if tok == "auto":
|
if tok == "auto":
|
||||||
tok = _detect_archive_pq(arc) or "pq"
|
tok = _detect_archive_pq(arc) or "pq"
|
||||||
_, flag = _PQ_FLAG[tok]
|
_, flag = _PQ_FLAG[tok]
|
||||||
|
|
@ -822,7 +833,7 @@ class VerifyTab(QWidget):
|
||||||
v.addWidget(QLabel("Verify checksums or inspect archive metadata."))
|
v.addWidget(QLabel("Verify checksums or inspect archive metadata."))
|
||||||
v.addWidget(Sep())
|
v.addWidget(Sep())
|
||||||
v.addWidget(H("Verify integrity"))
|
v.addWidget(H("Verify integrity"))
|
||||||
self.varc = PathField("Archive to verify", filters="VaptVupt archive (*.zupt);;All (*)"); v.addWidget(self.varc)
|
self.varc = PathField("Archive to verify", filters="ZUPT archive (*.zupt);;All (*)"); v.addWidget(self.varc)
|
||||||
enc = QHBoxLayout(); enc.setSpacing(16)
|
enc = QHBoxLayout(); enc.setSpacing(16)
|
||||||
pw = QVBoxLayout(); pw.addWidget(H("Password (if encrypted)")); self.vpw = PwField("Leave empty if not encrypted"); pw.addWidget(self.vpw); enc.addLayout(pw)
|
pw = QVBoxLayout(); pw.addWidget(H("Password (if encrypted)")); self.vpw = PwField("Leave empty if not encrypted"); pw.addWidget(self.vpw); enc.addLayout(pw)
|
||||||
pq = QVBoxLayout(); pq.addWidget(H("PQ private key (if post-quantum)")); self.vpq = PathField("Auto-detected; needed for --pq / --pq-only archives", filters="Key (*.key);;All (*)"); pq.addWidget(self.vpq); enc.addLayout(pq)
|
pq = QVBoxLayout(); pq.addWidget(H("PQ private key (if post-quantum)")); self.vpq = PathField("Auto-detected; needed for --pq / --pq-only archives", filters="Key (*.key);;All (*)"); pq.addWidget(self.vpq); enc.addLayout(pq)
|
||||||
|
|
@ -836,7 +847,7 @@ class VerifyTab(QWidget):
|
||||||
self.vlog = Log(120); v.addWidget(self.vlog)
|
self.vlog = Log(120); v.addWidget(self.vlog)
|
||||||
v.addWidget(Sep())
|
v.addWidget(Sep())
|
||||||
v.addWidget(H("Archive info (no password needed)"))
|
v.addWidget(H("Archive info (no password needed)"))
|
||||||
self.iarc = PathField("Archive to inspect", filters="VaptVupt archive (*.zupt);;All (*)"); v.addWidget(self.iarc)
|
self.iarc = PathField("Archive to inspect", filters="ZUPT archive (*.zupt);;All (*)"); v.addWidget(self.iarc)
|
||||||
self.ibtn = QPushButton("Show Info"); self.ibtn.clicked.connect(self._info); v.addWidget(self.ibtn)
|
self.ibtn = QPushButton("Show Info"); self.ibtn.clicked.connect(self._info); v.addWidget(self.ibtn)
|
||||||
self.ilog = Log(140); v.addWidget(self.ilog); v.addStretch()
|
self.ilog = Log(140); v.addWidget(self.ilog); v.addStretch()
|
||||||
lay = QVBoxLayout(self); lay.setContentsMargins(0,0,0,0); lay.addWidget(scrollable(inner))
|
lay = QVBoxLayout(self); lay.setContentsMargins(0,0,0,0); lay.addWidget(scrollable(inner))
|
||||||
|
|
@ -844,7 +855,7 @@ class VerifyTab(QWidget):
|
||||||
def _verify(self):
|
def _verify(self):
|
||||||
arc = self.varc.path()
|
arc = self.varc.path()
|
||||||
if not arc:
|
if not arc:
|
||||||
QMessageBox.warning(self, "VaptVupt", "Select an archive to verify."); return
|
QMessageBox.warning(self, "ZUPT", "Select an archive to verify."); return
|
||||||
if not os.path.isfile(arc):
|
if not os.path.isfile(arc):
|
||||||
self.vlog.clear(); self.vlog.append(f"No such file: {arc}"); return
|
self.vlog.clear(); self.vlog.append(f"No such file: {arc}"); return
|
||||||
self.vlog.clear()
|
self.vlog.clear()
|
||||||
|
|
@ -860,7 +871,7 @@ class VerifyTab(QWidget):
|
||||||
"Enter the password above, then click Verify again.")
|
"Enter the password above, then click Verify again.")
|
||||||
return
|
return
|
||||||
cmd += ["-p", self.vpw.text()]
|
cmd += ["-p", self.vpw.text()]
|
||||||
elif kind in ("pq", "pqonly", "sdk"):
|
elif kind in ("pq", "pqonly", "sdk", "box"):
|
||||||
if not self.vpq.path():
|
if not self.vpq.path():
|
||||||
self.vlog.append(f"This archive uses {label} encryption.\n"
|
self.vlog.append(f"This archive uses {label} encryption.\n"
|
||||||
"Select the matching private key above, then click Verify again.")
|
"Select the matching private key above, then click Verify again.")
|
||||||
|
|
@ -899,7 +910,7 @@ class DiskTab(QWidget):
|
||||||
v.addWidget(H("Backup — source device or image"))
|
v.addWidget(H("Backup — source device or image"))
|
||||||
self.bsrc = PathField("/dev/sdX or disk.img"); v.addWidget(self.bsrc)
|
self.bsrc = PathField("/dev/sdX or disk.img"); v.addWidget(self.bsrc)
|
||||||
v.addWidget(H("Backup — output archive"))
|
v.addWidget(H("Backup — output archive"))
|
||||||
self.bout = PathField("backup.zupt", "save", "VaptVupt archive (*.zupt);;All (*)"); v.addWidget(self.bout)
|
self.bout = PathField("backup.zupt", "save", "ZUPT archive (*.zupt);;All (*)"); v.addWidget(self.bout)
|
||||||
bopt = QHBoxLayout(); bopt.setSpacing(16)
|
bopt = QHBoxLayout(); bopt.setSpacing(16)
|
||||||
oc = QVBoxLayout(); oc.addWidget(H("Options")); self.bdedup = QCheckBox("Block deduplication"); oc.addWidget(self.bdedup); bopt.addLayout(oc)
|
oc = QVBoxLayout(); oc.addWidget(H("Options")); self.bdedup = QCheckBox("Block deduplication"); oc.addWidget(self.bdedup); bopt.addLayout(oc)
|
||||||
pc = QVBoxLayout(); pc.addWidget(H("Password")); self.bpw = PwField("Optional — AES-256"); pc.addWidget(self.bpw); bopt.addLayout(pc)
|
pc = QVBoxLayout(); pc.addWidget(H("Password")); self.bpw = PwField("Optional — AES-256"); pc.addWidget(self.bpw); bopt.addLayout(pc)
|
||||||
|
|
@ -908,7 +919,7 @@ class DiskTab(QWidget):
|
||||||
self.blog = Log(100); v.addWidget(self.blog)
|
self.blog = Log(100); v.addWidget(self.blog)
|
||||||
v.addWidget(Sep())
|
v.addWidget(Sep())
|
||||||
v.addWidget(H("Restore — archive"))
|
v.addWidget(H("Restore — archive"))
|
||||||
self.rarc = PathField("backup.zupt", filters="VaptVupt archive (*.zupt);;All (*)"); v.addWidget(self.rarc)
|
self.rarc = PathField("backup.zupt", filters="ZUPT archive (*.zupt);;All (*)"); v.addWidget(self.rarc)
|
||||||
v.addWidget(H("Restore — target device or file"))
|
v.addWidget(H("Restore — target device or file"))
|
||||||
self.rtgt = PathField("/dev/sdX or output.img", "save"); v.addWidget(self.rtgt)
|
self.rtgt = PathField("/dev/sdX or output.img", "save"); v.addWidget(self.rtgt)
|
||||||
v.addWidget(H("Restore — password"))
|
v.addWidget(H("Restore — password"))
|
||||||
|
|
@ -919,7 +930,7 @@ class DiskTab(QWidget):
|
||||||
|
|
||||||
def _backup(self):
|
def _backup(self):
|
||||||
s, o = self.bsrc.path(), self.bout.path()
|
s, o = self.bsrc.path(), self.bout.path()
|
||||||
if not s or not o: QMessageBox.warning(self, "VaptVupt", "Set source and output."); return
|
if not s or not o: QMessageBox.warning(self, "ZUPT", "Set source and output."); return
|
||||||
cmd = ["disk", "backup"]
|
cmd = ["disk", "backup"]
|
||||||
if self.bdedup.isChecked(): cmd.append("--dedup")
|
if self.bdedup.isChecked(): cmd.append("--dedup")
|
||||||
if self.bpw.text(): cmd += ["-p", self.bpw.text()]
|
if self.bpw.text(): cmd += ["-p", self.bpw.text()]
|
||||||
|
|
@ -927,7 +938,7 @@ class DiskTab(QWidget):
|
||||||
|
|
||||||
def _restore(self):
|
def _restore(self):
|
||||||
a, t = self.rarc.path(), self.rtgt.path()
|
a, t = self.rarc.path(), self.rtgt.path()
|
||||||
if not a or not t: QMessageBox.warning(self, "VaptVupt", "Set archive and target."); return
|
if not a or not t: QMessageBox.warning(self, "ZUPT", "Set archive and target."); return
|
||||||
SB = QMessageBox.StandardButton
|
SB = QMessageBox.StandardButton
|
||||||
if QMessageBox.warning(self, "Confirm", f"OVERWRITE {t}?", SB.Yes|SB.Cancel) != SB.Yes: return
|
if QMessageBox.warning(self, "Confirm", f"OVERWRITE {t}?", SB.Yes|SB.Cancel) != SB.Yes: return
|
||||||
cmd = ["disk", "restore"]
|
cmd = ["disk", "restore"]
|
||||||
|
|
@ -941,14 +952,14 @@ class AboutTab(QWidget):
|
||||||
inner = QWidget()
|
inner = QWidget()
|
||||||
v = QVBoxLayout(inner); v.setContentsMargins(24,24,24,24); v.setSpacing(4)
|
v = QVBoxLayout(inner); v.setContentsMargins(24,24,24,24); v.setSpacing(4)
|
||||||
for text, style in [
|
for text, style in [
|
||||||
("VAPTVUPT", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
("ZUPT", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||||
(ZUPT_VER_NUMBER, "color:white;font-size:28px;font-weight:800;font-family:monospace;"),
|
(ZUPT_VER_NUMBER, "color:white;font-size:28px;font-weight:800;font-family:monospace;"),
|
||||||
("", ""),
|
("", ""),
|
||||||
("Post-quantum backup compression with ML-KEM-768: --pq hybrid", "color:#6a8898;font-size:13px;"),
|
("Post-quantum backup compression with ML-KEM-768: --pq hybrid", "color:#6a8898;font-size:13px;"),
|
||||||
(f"(+ X25519) or --pq-only (pure). {DEFAULT_KDF} password KDF,", "color:#6a8898;font-size:13px;"),
|
(f"(+ X25519) or --pq-only (pure). {DEFAULT_KDF} password KDF,", "color:#6a8898;font-size:13px;"),
|
||||||
("block deduplication, and full-disk backup.", "color:#6a8898;font-size:13px;"),
|
("block deduplication, and full-disk backup.", "color:#6a8898;font-size:13px;"),
|
||||||
("Renamed from Zupt in v3.0.0 (INPI Brasil trademark); .zupt", "color:#6a8898;font-size:13px;"),
|
("Original ZUPT name restored in 5.2.2; the .zupt extension", "color:#6a8898;font-size:13px;"),
|
||||||
("archive extension and v1.6 wire format are unchanged.", "color:#6a8898;font-size:13px;"),
|
("and v1.6 version byte remain; 5.2.2 adds flag-gated records.", "color:#6a8898;font-size:13px;"),
|
||||||
("", ""),
|
("", ""),
|
||||||
("CRYPTOGRAPHIC STACK", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
("CRYPTOGRAPHIC STACK", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||||
("ML-KEM-768 FIPS 203 Post-Quantum KEM", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("ML-KEM-768 FIPS 203 Post-Quantum KEM", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
|
|
@ -963,22 +974,21 @@ class AboutTab(QWidget):
|
||||||
("XXH64 (non-crypto) Per-block checksum (inside AEAD)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("XXH64 (non-crypto) Per-block checksum (inside AEAD)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("", ""),
|
("", ""),
|
||||||
("COMPRESSION CODEC", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
("COMPRESSION CODEC", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||||
("VaptVupt LZ + ANS 2.60.4 LZ77 + tabled ANS entropy", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("VaptVupt LZ + ANS 2.65.3 LZ77 + tabled ANS entropy", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("AVX2 / NEON SIMD acceleration; CBMC-verified BCJ filters", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("AVX2 / NEON SIMD acceleration; portable scalar fallbacks", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("", ""),
|
("", ""),
|
||||||
("CREDITS", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
("CREDITS", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||||
("VaptVupt application Cristian Cezar Moisés", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("ZUPT application Cristian Cezar Moisés", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
(" License: AGPL-3.0-or-later (commercial license available)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
(" License: AGPL-3.0-or-later (commercial terms may be available)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
(" git.securityops.co/cristiancmoises/vaptvupt", "color:#3a5868;font-size:11px;font-family:monospace;"),
|
(" github.com/cristiancmoises/zupt", "color:#3a5868;font-size:11px;font-family:monospace;"),
|
||||||
("", ""),
|
("", ""),
|
||||||
("VaptVupt LZ + ANS codec Cristian Cezar Moisés", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("VaptVupt LZ + ANS codec Cristian Cezar Moisés", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
(" License: GPL-3.0-or-later (commercial license available)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
(" License: GPL-3.0-or-later (commercial terms may be available)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
(" git.securityops.co/cristiancmoises/vaptvupt", "color:#3a5868;font-size:11px;font-family:monospace;"),
|
(" github.com/cristiancmoises/zupt", "color:#3a5868;font-size:11px;font-family:monospace;"),
|
||||||
("", ""),
|
("", ""),
|
||||||
("WEBSITE & CONTACT", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
("WEBSITE & CONTACT", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||||
("https://zupt.securityops.co", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("https://github.com/cristiancmoises/zupt", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("sac@securityops.co (commercial licensing)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("sac@securityops.co (commercial-terms inquiries)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("zupt@riseup.net (general / bugs)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
|
||||||
("", ""),
|
("", ""),
|
||||||
(ZUPT_VER_SHORT, "color:#3a5868;font-size:11px;font-family:monospace;"),
|
(ZUPT_VER_SHORT, "color:#3a5868;font-size:11px;font-family:monospace;"),
|
||||||
]:
|
]:
|
||||||
|
|
@ -995,7 +1005,7 @@ class AboutTab(QWidget):
|
||||||
class ZuptWindow(QMainWindow):
|
class ZuptWindow(QMainWindow):
|
||||||
def __init__(self, compress_files=None, extract_file=None):
|
def __init__(self, compress_files=None, extract_file=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.setWindowTitle(f"VaptVupt {ZUPT_VER_NUMBER}")
|
self.setWindowTitle(f"ZUPT {ZUPT_VER_NUMBER}")
|
||||||
self.setMinimumSize(720, 500)
|
self.setMinimumSize(720, 500)
|
||||||
self.resize(880, 640)
|
self.resize(880, 640)
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
|
|
@ -1010,7 +1020,7 @@ class ZuptWindow(QMainWindow):
|
||||||
# Header
|
# Header
|
||||||
hdr = QFrame(); hdr.setStyleSheet("background:#050a0e;border-bottom:1px solid #1a2a30;")
|
hdr = QFrame(); hdr.setStyleSheet("background:#050a0e;border-bottom:1px solid #1a2a30;")
|
||||||
hl = QHBoxLayout(hdr); hl.setContentsMargins(20,10,20,10)
|
hl = QHBoxLayout(hdr); hl.setContentsMargins(20,10,20,10)
|
||||||
title = QLabel("VAPTVUPT"); title.setStyleSheet("color:white;font-size:15px;font-weight:800;letter-spacing:3px;")
|
title = QLabel("ZUPT"); title.setStyleSheet("color:white;font-size:15px;font-weight:800;letter-spacing:3px;")
|
||||||
hl.addWidget(title)
|
hl.addWidget(title)
|
||||||
sub = QLabel("Post-Quantum Backup"); sub.setStyleSheet("color:#3a5868;font-size:10px;font-weight:600;letter-spacing:1px;margin-left:8px;")
|
sub = QLabel("Post-Quantum Backup"); sub.setStyleSheet("color:#3a5868;font-size:10px;font-weight:600;letter-spacing:1px;margin-left:8px;")
|
||||||
hl.addWidget(sub); hl.addStretch()
|
hl.addWidget(sub); hl.addStretch()
|
||||||
|
|
@ -1033,7 +1043,7 @@ class ZuptWindow(QMainWindow):
|
||||||
layout.addWidget(self.tabs)
|
layout.addWidget(self.tabs)
|
||||||
|
|
||||||
sb = QStatusBar()
|
sb = QStatusBar()
|
||||||
sb.showMessage(f"VaptVupt {ZUPT_VER_NUMBER} | {VAPTVUPT}")
|
sb.showMessage(f"ZUPT {ZUPT_VER_NUMBER} | {ZUPT_CLI}")
|
||||||
self.setStatusBar(sb)
|
self.setStatusBar(sb)
|
||||||
|
|
||||||
def dragEnterEvent(self, e):
|
def dragEnterEvent(self, e):
|
||||||
|
|
@ -1058,7 +1068,7 @@ class ZuptWindow(QMainWindow):
|
||||||
# leaves the target half-written), so never do it silently.
|
# leaves the target half-written), so never do it silently.
|
||||||
SB = QMessageBox.StandardButton
|
SB = QMessageBox.StandardButton
|
||||||
if QMessageBox.warning(
|
if QMessageBox.warning(
|
||||||
self, "VaptVupt",
|
self, "ZUPT",
|
||||||
"An operation is still running.\nQuit and abort it?",
|
"An operation is still running.\nQuit and abort it?",
|
||||||
SB.Yes | SB.Cancel) != SB.Yes:
|
SB.Yes | SB.Cancel) != SB.Yes:
|
||||||
e.ignore(); return
|
e.ignore(); return
|
||||||
|
|
@ -1081,19 +1091,19 @@ class ZuptWindow(QMainWindow):
|
||||||
def main():
|
def main():
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
|
|
||||||
# Lightweight non-GUI flags first, so `vaptvupt-gui --version|--help|--selftest`
|
# Lightweight non-GUI flags first, so `zupt-gui --version|--help|--selftest`
|
||||||
# work with no display and aren't mistaken for files to compress. `--selftest`
|
# work with no display and aren't mistaken for files to compress. `--selftest`
|
||||||
# is a headless-friendly smoke test: it builds the whole UI and spins the event
|
# is a headless-friendly smoke test: it builds the whole UI and spins the event
|
||||||
# loop once, then exits 0 — the reliable way to confirm the GUI stack launches
|
# loop once, then exits 0 — the reliable way to confirm the GUI stack launches
|
||||||
# on a machine where the window itself is hard to see (tiling WM, remote, CI).
|
# on a machine where the window itself is hard to see (tiling WM, remote, CI).
|
||||||
if args and args[0] in ("--version", "-V", "version"):
|
if args and args[0] in ("--version", "-V", "version"):
|
||||||
print(f"vaptvupt-gui {ZUPT_VER_NUMBER} ({QT_BINDING}) | CLI: {VAPTVUPT}")
|
print(f"zupt-gui {ZUPT_VER_NUMBER} ({QT_BINDING}) | CLI: {ZUPT_CLI}")
|
||||||
return 0
|
return 0
|
||||||
if args and args[0] in ("--help", "-h", "help"):
|
if args and args[0] in ("--help", "-h", "help"):
|
||||||
print("usage: vaptvupt-gui [ARCHIVE.zupt | --extract ARCHIVE.zupt |\n"
|
print("usage: zupt-gui [ARCHIVE.zupt | --extract ARCHIVE.zupt |\n"
|
||||||
" --compress FILE [FILE ...]]\n"
|
" --compress FILE [FILE ...]]\n"
|
||||||
" vaptvupt-gui --selftest # verify the GUI launches (no window kept)\n"
|
" zupt-gui --selftest # verify the GUI launches (no window kept)\n"
|
||||||
" vaptvupt-gui --version")
|
" zupt-gui --version")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
compress_files = extract_file = None
|
compress_files = extract_file = None
|
||||||
|
|
@ -1105,7 +1115,7 @@ def main():
|
||||||
else: compress_files = args
|
else: compress_files = args
|
||||||
|
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
app.setApplicationName("VaptVupt")
|
app.setApplicationName("ZUPT")
|
||||||
if ICON_PATH: app.setWindowIcon(QIcon(ICON_PATH))
|
if ICON_PATH: app.setWindowIcon(QIcon(ICON_PATH))
|
||||||
app.setStyle("Fusion")
|
app.setStyle("Fusion")
|
||||||
app.setStyleSheet(STYLE)
|
app.setStyleSheet(STYLE)
|
||||||
|
|
@ -1126,7 +1136,7 @@ def main():
|
||||||
QTimer.singleShot(400, app.quit)
|
QTimer.singleShot(400, app.quit)
|
||||||
rc = app.exec()
|
rc = app.exec()
|
||||||
print(f"selftest OK — {QT_BINDING}: window + {win.tabs.count()} tabs built, "
|
print(f"selftest OK — {QT_BINDING}: window + {win.tabs.count()} tabs built, "
|
||||||
f"event loop ran (rc={rc}); CLI={VAPTVUPT}")
|
f"event loop ran (rc={rc}); CLI={ZUPT_CLI}")
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
# Center + raise + focus ONLY on X11 (xcb), where a stacking WM may place
|
# Center + raise + focus ONLY on X11 (xcb), where a stacking WM may place
|
||||||
|
|
@ -1162,7 +1172,7 @@ def main():
|
||||||
# is unaffected. The sentinel env var prevents any relaunch loop (e.g.
|
# is unaffected. The sentinel env var prevents any relaunch loop (e.g.
|
||||||
# "-platform wayland" in argv outranks the env override and would come up
|
# "-platform wayland" in argv outranks the env override and would come up
|
||||||
# wayland again). Nothing auto-starts jobs before the deadline, so the exec
|
# wayland again). Nothing auto-starts jobs before the deadline, so the exec
|
||||||
# cannot interrupt real work. Opt out with VAPTVUPT_NO_XCB_FALLBACK=1.
|
# cannot interrupt real work. Opt out with ZUPT_NO_XCB_FALLBACK=1.
|
||||||
if app.platformName().startswith("wayland"):
|
if app.platformName().startswith("wayland"):
|
||||||
class _ExposeLatch(QObject):
|
class _ExposeLatch(QObject):
|
||||||
exposed_once = False
|
exposed_once = False
|
||||||
|
|
@ -1177,10 +1187,14 @@ def main():
|
||||||
def _wayland_map_check():
|
def _wayland_map_check():
|
||||||
if latch.exposed_once or (handle is not None and handle.isExposed()):
|
if latch.exposed_once or (handle is not None and handle.isExposed()):
|
||||||
return
|
return
|
||||||
|
no_fallback = (os.environ.get("ZUPT_NO_XCB_FALLBACK")
|
||||||
|
or os.environ.get("VAPTVUPT_NO_XCB_FALLBACK"))
|
||||||
|
fallback_done = (os.environ.get("ZUPT_XCB_FALLBACK_DONE")
|
||||||
|
or os.environ.get("VAPTVUPT_XCB_FALLBACK_DONE"))
|
||||||
can_fallback = (os.environ.get("DISPLAY")
|
can_fallback = (os.environ.get("DISPLAY")
|
||||||
and sys.executable
|
and sys.executable
|
||||||
and os.environ.get("VAPTVUPT_NO_XCB_FALLBACK") != "1"
|
and no_fallback != "1"
|
||||||
and os.environ.get("VAPTVUPT_XCB_FALLBACK_DONE") != "1")
|
and fallback_done != "1")
|
||||||
if sys.stderr is not None:
|
if sys.stderr is not None:
|
||||||
try:
|
try:
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
|
|
@ -1195,7 +1209,7 @@ def main():
|
||||||
pass
|
pass
|
||||||
if can_fallback:
|
if can_fallback:
|
||||||
env = dict(os.environ, QT_QPA_PLATFORM="xcb",
|
env = dict(os.environ, QT_QPA_PLATFORM="xcb",
|
||||||
VAPTVUPT_XCB_FALLBACK_DONE="1")
|
ZUPT_XCB_FALLBACK_DONE="1")
|
||||||
argv = (list(sys.argv) if getattr(sys, "frozen", False)
|
argv = (list(sys.argv) if getattr(sys, "frozen", False)
|
||||||
else [sys.executable] + sys.argv)
|
else [sys.executable] + sys.argv)
|
||||||
try:
|
try:
|
||||||
|
|
@ -1217,7 +1231,7 @@ def main():
|
||||||
# a courtesy notice must never take the GUI down.
|
# a courtesy notice must never take the GUI down.
|
||||||
if sys.stderr is not None:
|
if sys.stderr is not None:
|
||||||
try:
|
try:
|
||||||
sys.stderr.write(f"VaptVupt {ZUPT_VER_NUMBER} GUI started — "
|
sys.stderr.write(f"ZUPT {ZUPT_VER_NUMBER} GUI started — "
|
||||||
f"window open (close it to exit).\n")
|
f"window open (close it to exit).\n")
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
except OSError:
|
except OSError:
|
||||||
|
|
|
||||||
65
gui/zupt-gui
65
gui/zupt-gui
|
|
@ -1,46 +1,31 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
# Source-tree launcher for ZUPT GUI.
|
||||||
VENV="$DIR/.venv"
|
# It performs no package installation and never downloads dependencies.
|
||||||
PY="$VENV/bin/python3"
|
set -Eeuo pipefail
|
||||||
PIP="$VENV/bin/pip"
|
|
||||||
GUI="$DIR/src/zupt_gui.py"
|
|
||||||
|
|
||||||
# ─── System deps (Qt xcb needs these on Debian/Mint/Ubuntu) ───
|
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||||
NEED_APT=0
|
gui=$script_dir/src/zupt_gui.py
|
||||||
for pkg in libxcb-cursor0 libxcb-xinerama0 libxkbcommon-x11-0 libegl1 python3-full; do
|
[[ -f $gui ]] || {
|
||||||
dpkg -s "$pkg" >/dev/null 2>&1 || NEED_APT=1
|
printf 'zupt-gui: GUI source is missing: %s\n' "$gui" >&2
|
||||||
done
|
exit 1
|
||||||
if [ "$NEED_APT" -eq 1 ]; then
|
}
|
||||||
echo "Installing system dependencies..."
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -y python3-full python3-venv \
|
|
||||||
libxcb-cursor0 libxcb-xinerama0 libxkbcommon-x11-0 libegl1 2>/dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ─── Venv ───
|
if [[ -z ${ZUPT_BIN:-} ]]; then
|
||||||
if [ ! -x "$PY" ]; then
|
if [[ -n ${VAPTVUPT_BIN:-} ]]; then
|
||||||
rm -rf "$VENV"
|
export ZUPT_BIN=$VAPTVUPT_BIN
|
||||||
python3 -m venv "$VENV"
|
elif [[ -x $script_dir/../zupt ]]; then
|
||||||
fi
|
export ZUPT_BIN=$script_dir/../zupt
|
||||||
if ! "$PY" -c "import PySide6" 2>/dev/null; then
|
elif command -v zupt >/dev/null 2>&1; then
|
||||||
echo "Installing PySide6..."
|
ZUPT_BIN=$(command -v zupt)
|
||||||
"$PIP" install --upgrade pip -q 2>/dev/null
|
export ZUPT_BIN
|
||||||
"$PIP" install PySide6 -q
|
elif [[ -x $script_dir/../vaptvupt ]]; then
|
||||||
fi
|
export ZUPT_BIN=$script_dir/../vaptvupt
|
||||||
|
elif command -v vaptvupt >/dev/null 2>&1; then
|
||||||
# ─── Find zupt — local build FIRST, then system ───
|
ZUPT_BIN=$(command -v vaptvupt)
|
||||||
if [ -z "$ZUPT_BIN" ]; then
|
export ZUPT_BIN
|
||||||
# Check project tree first (gui/ is inside zupt-2.1.6/)
|
|
||||||
for p in "$DIR/../zupt" "$DIR/../../zupt" "$DIR/zupt"; do
|
|
||||||
[ -x "$p" ] && export ZUPT_BIN="$(readlink -f "$p")" && break
|
|
||||||
done
|
|
||||||
# Then system PATH
|
|
||||||
if [ -z "$ZUPT_BIN" ]; then
|
|
||||||
p="$(command -v zupt 2>/dev/null)"
|
|
||||||
[ -x "$p" ] && export ZUPT_BIN="$p"
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec "$PY" "$GUI" "$@"
|
exec python3 "$gui" "$@"
|
||||||
|
|
|
||||||
211
include/zupt.h
211
include/zupt.h
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Zupt — Backup-oriented compression with AES-256 encryption
|
* ZUPT — Backup-oriented compression with AES-256 encryption
|
||||||
* Copyright (c) 2026 Cristian Cezar Moisés
|
* Copyright (c) 2026 Cristian Cezar Moisés
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
@ -15,12 +15,79 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <direct.h>
|
#include <direct.h>
|
||||||
|
#include <io.h>
|
||||||
|
#include <wchar.h>
|
||||||
#define ZUPT_PATH_SEP '\\'
|
#define ZUPT_PATH_SEP '\\'
|
||||||
#define zupt_mkdir(p) _mkdir(p)
|
|
||||||
|
static inline wchar_t *zupt_win_utf8_to_wide_alloc(const char *text) {
|
||||||
|
if (!text) return NULL;
|
||||||
|
int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
|
||||||
|
text, -1, NULL, 0);
|
||||||
|
if (length <= 0) return NULL;
|
||||||
|
wchar_t *wide = (wchar_t *)malloc((size_t)length * sizeof(wchar_t));
|
||||||
|
if (!wide || !MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
|
||||||
|
text, -1, wide, length)) {
|
||||||
|
free(wide);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return wide;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char *zupt_win_wide_to_utf8_alloc(const wchar_t *text) {
|
||||||
|
if (!text) return NULL;
|
||||||
|
int length = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
|
||||||
|
text, -1, NULL, 0, NULL, NULL);
|
||||||
|
if (length <= 0) return NULL;
|
||||||
|
char *utf8 = (char *)malloc((size_t)length);
|
||||||
|
if (!utf8 || !WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,
|
||||||
|
text, -1, utf8, length, NULL, NULL)) {
|
||||||
|
free(utf8);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return utf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline FILE *zupt_win_fopen_utf8(const char *path, const char *mode) {
|
||||||
|
wchar_t *wide_path = zupt_win_utf8_to_wide_alloc(path);
|
||||||
|
wchar_t *wide_mode = zupt_win_utf8_to_wide_alloc(mode);
|
||||||
|
if (!wide_path || !wide_mode) {
|
||||||
|
free(wide_path);
|
||||||
|
free(wide_mode);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
FILE *stream = _wfopen(wide_path, wide_mode);
|
||||||
|
free(wide_path);
|
||||||
|
free(wide_mode);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline DWORD zupt_win_get_attributes_utf8(const char *path) {
|
||||||
|
wchar_t *wide = zupt_win_utf8_to_wide_alloc(path);
|
||||||
|
if (!wide) return INVALID_FILE_ATTRIBUTES;
|
||||||
|
DWORD attributes = GetFileAttributesW(wide);
|
||||||
|
free(wide);
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int zupt_win_mkdir_utf8(const char *path) {
|
||||||
|
wchar_t *wide = zupt_win_utf8_to_wide_alloc(path);
|
||||||
|
if (!wide) return -1;
|
||||||
|
int result = _wmkdir(wide);
|
||||||
|
free(wide);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Project path strings are UTF-8 on every platform. Call this wrapper
|
||||||
|
* explicitly; never rewrite the C library's fopen in consumer code. */
|
||||||
|
static inline FILE *zupt_fopen_path(const char *path, const char *mode) {
|
||||||
|
return zupt_win_fopen_utf8(path, mode);
|
||||||
|
}
|
||||||
|
#define zupt_mkdir(p) zupt_win_mkdir_utf8(p)
|
||||||
#else
|
#else
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
@ -28,29 +95,29 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#define ZUPT_PATH_SEP '/'
|
#define ZUPT_PATH_SEP '/'
|
||||||
#define zupt_mkdir(p) mkdir(p, 0755)
|
#define zupt_mkdir(p) mkdir(p, 0755)
|
||||||
|
static inline FILE *zupt_fopen_path(const char *path, const char *mode) {
|
||||||
|
return fopen(path, mode);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* ─── Product identity ─────────────────────────────────────────────
|
/* ─── Product identity ─────────────────────────────────────────────
|
||||||
*
|
*
|
||||||
* v3.0.0 (INPI Brasil trademark rename):
|
* v5.2.2 (product identity restored):
|
||||||
* - Product name is now "VaptVupt" (was "Zupt"). The earlier name
|
* - The public product and primary command are again "ZUPT" and `zupt`.
|
||||||
* conflicted with a software trademark already registered at INPI
|
* - On-disk compatibility is deliberately unchanged: magic remains
|
||||||
* Brasil under "Zupt".
|
* "ZUPT", the archive extension remains .zupt, and format version
|
||||||
* - File extension stays `.zupt` for archive-format continuity:
|
* remains 1.6.
|
||||||
* v1.0–v2.4.x archives remain readable, the magic bytes
|
* - Internal zupt_* symbols, SDK identifiers, codec IDs, and the bundled
|
||||||
* `\x5A\x55\x50\x54\x1A\x00` ("ZUPT" + sub-version) are unchanged.
|
* VaptVupt codec ABI remain unchanged.
|
||||||
* - C identifier prefix stays `zupt_` / `ZUPT_` for ABI continuity
|
* - Distributors may offer `vaptvupt -> zupt` only as an explicit
|
||||||
* with libzuptsdk and existing callers. Only user-visible strings
|
* compatibility alias for scripts written for releases 3.0.0--5.2.1.
|
||||||
* (binary name, banner, help text, package names) change.
|
|
||||||
* - The binary is now `vaptvupt`. Distro packages may ship a
|
|
||||||
* compatibility symlink `zupt -> vaptvupt` for one major version.
|
|
||||||
*/
|
*/
|
||||||
#define ZUPT_PRODUCT_NAME "VaptVupt"
|
#define ZUPT_PRODUCT_NAME "ZUPT"
|
||||||
#define ZUPT_PRODUCT_NAME_LC "vaptvupt" /* lowercase: binary name */
|
#define ZUPT_PRODUCT_NAME_LC "zupt" /* lowercase: binary name */
|
||||||
#define ZUPT_PRODUCT_EXTENSION ".zupt" /* on-disk archive extension (kept stable) */
|
#define ZUPT_PRODUCT_EXTENSION ".zupt" /* on-disk archive extension (kept stable) */
|
||||||
#define ZUPT_PRODUCT_TAGLINE "Post-quantum backup compression"
|
#define ZUPT_PRODUCT_TAGLINE "Post-quantum backup compression"
|
||||||
|
|
||||||
#define ZUPT_VERSION_STRING "5.2.1"
|
#define ZUPT_VERSION_STRING "5.2.2"
|
||||||
/* Vendored codec release (upstream tag) — single source for display strings.
|
/* Vendored codec release (upstream tag) — single source for display strings.
|
||||||
* The codec's own VV_VERSION_* is its internal API version, not the release. */
|
* The codec's own VV_VERSION_* is its internal API version, not the release. */
|
||||||
#define ZUPT_CODEC_RELEASE "2.65.3"
|
#define ZUPT_CODEC_RELEASE "2.65.3"
|
||||||
|
|
@ -67,10 +134,13 @@
|
||||||
* to keep the field stable across format-version transitions. Both bytes are
|
* to keep the field stable across format-version transitions. Both bytes are
|
||||||
* structurally validated by read_footer().
|
* structurally validated by read_footer().
|
||||||
*
|
*
|
||||||
* Read path falls back to v1.4 layout (no trailer) when the footer magic is
|
* The reader can identify a legacy v1.4 layout at EOF-32, but refuses it by
|
||||||
* found at EOF-32 instead of EOF-64. */
|
* default because the missing trailer is indistinguishable from an integrity
|
||||||
|
* downgrade. Trusted old archives require --allow-legacy-no-ait. */
|
||||||
#define ZUPT_AIT_SIZE 32
|
#define ZUPT_AIT_SIZE 32
|
||||||
#define ZUPT_AIT_MAC_INPUT_LEN (sizeof(zupt_archive_header_t) + 24)
|
#define ZUPT_ARCHIVE_HEADER_SIZE 64u
|
||||||
|
#define ZUPT_FOOTER_SIZE 32u
|
||||||
|
#define ZUPT_AIT_MAC_INPUT_LEN (ZUPT_ARCHIVE_HEADER_SIZE + 24u)
|
||||||
|
|
||||||
#define ZUPT_MAGIC_0 0x5A
|
#define ZUPT_MAGIC_0 0x5A
|
||||||
#define ZUPT_MAGIC_1 0x55
|
#define ZUPT_MAGIC_1 0x55
|
||||||
|
|
@ -83,6 +153,11 @@
|
||||||
|
|
||||||
#define ZUPT_MAX_PATH 4096
|
#define ZUPT_MAX_PATH 4096
|
||||||
#define ZUPT_MAX_FILES 2000000
|
#define ZUPT_MAX_FILES 2000000
|
||||||
|
/* A decoded index entry contains a fixed-size path buffer. Cap aggregate
|
||||||
|
* allocation independently of the wire count so a compact malicious index
|
||||||
|
* cannot request several gigabytes of zeroed memory. */
|
||||||
|
#define ZUPT_MAX_INDEX_ALLOC_BYTES (256u * 1024u * 1024u)
|
||||||
|
#define ZUPT_MIN_INDEX_ENTRY_BYTES 47u
|
||||||
#define ZUPT_DEFAULT_BLOCK_SZ (4 * 1024 * 1024)
|
#define ZUPT_DEFAULT_BLOCK_SZ (4 * 1024 * 1024)
|
||||||
#define ZUPT_MIN_BLOCK_SZ (64 * 1024)
|
#define ZUPT_MIN_BLOCK_SZ (64 * 1024)
|
||||||
#define ZUPT_MAX_BLOCK_SZ (256 * 1024 * 1024)
|
#define ZUPT_MAX_BLOCK_SZ (256 * 1024 * 1024)
|
||||||
|
|
@ -97,12 +172,14 @@
|
||||||
#define ZUPT_FLAG_DEDUP (1u << 7) /* Block-level deduplication enabled */
|
#define ZUPT_FLAG_DEDUP (1u << 7) /* Block-level deduplication enabled */
|
||||||
#define ZUPT_FLAG_AAD_SEQ (1u << 8) /* MAC binds block_seq as AAD (anti-reorder) */
|
#define ZUPT_FLAG_AAD_SEQ (1u << 8) /* MAC binds block_seq as AAD (anti-reorder) */
|
||||||
#define ZUPT_FLAG_AAD_PREFACE (1u << 9) /* v1.6: MAC also binds per-block frame preface (F-09) */
|
#define ZUPT_FLAG_AAD_PREFACE (1u << 9) /* v1.6: MAC also binds per-block frame preface (F-09) */
|
||||||
|
#define ZUPT_FLAG_AUTH_DEDUP_REFS (1u << 10) /* Dedup offsets carry per-block authentication */
|
||||||
|
#define ZUPT_FLAG_DISK_CONTENT_HASH (1u << 11) /* Disk index hashes restored bytes */
|
||||||
|
|
||||||
/* Encryption types (stored in encryption header block) */
|
/* Encryption types (stored in encryption header block) */
|
||||||
#define ZUPT_ENC_PBKDF2 0x01 /* Password-based: PBKDF2 → AES-256-CTR + HMAC */
|
#define ZUPT_ENC_PBKDF2 0x01 /* Password-based: PBKDF2 → AES-256-CTR + HMAC */
|
||||||
#define ZUPT_ENC_PQ_HYBRID 0x02 /* ML-KEM-768 + X25519 hybrid KEM (legacy XOR+SHA3) */
|
#define ZUPT_ENC_PQ_HYBRID 0x02 /* ML-KEM-768 + X25519 hybrid KEM (legacy XOR+SHA3) */
|
||||||
#define ZUPT_ENC_PQ_SDK_V2 0x03 /* libzuptsdk v2 header: HKDF combiner + commitment + HPKE binding */
|
#define ZUPT_ENC_PQ_SDK_V2 0x03 /* libvuptsdk v2 header: HKDF combiner + commitment + HPKE binding */
|
||||||
#define ZUPT_ENC_PW_ARGON2 0x04 /* Password-based via libzuptsdk: Argon2id + XChaCha20-Poly1305 */
|
#define ZUPT_ENC_PW_ARGON2 0x04 /* Password-based via libvuptsdk: Argon2id + XChaCha20-Poly1305 */
|
||||||
#define ZUPT_ENC_PQ_BOX_V1 0x05 /* libpqvaptvupt sealed box: HKDF-SHA256 domain-separated combiner */
|
#define ZUPT_ENC_PQ_BOX_V1 0x05 /* libpqvaptvupt sealed box: HKDF-SHA256 domain-separated combiner */
|
||||||
#define ZUPT_ENC_PQ_ONLY 0x06 /* Full post-quantum: ML-KEM-768 only (no X25519), SHA3-512 KDF (v4.2.0) */
|
#define ZUPT_ENC_PQ_ONLY 0x06 /* Full post-quantum: ML-KEM-768 only (no X25519), SHA3-512 KDF (v4.2.0) */
|
||||||
|
|
||||||
|
|
@ -123,12 +200,12 @@
|
||||||
* descriptor is covered by the archive-integrity trailer (F-08), so it
|
* descriptor is covered by the archive-integrity trailer (F-08), so it
|
||||||
* cannot be stripped or forged without failing authentication.
|
* cannot be stripped or forged without failing authentication.
|
||||||
*
|
*
|
||||||
* Profile 0 (implicit, absent byte) == the historical libzuptsdk
|
* Profile 0 (implicit, absent byte) == the historical libvuptsdk
|
||||||
* "MODERATE" Argon2id preset reached via zuptsdk_easy_derive_key.
|
* "MODERATE" Argon2id preset reached via zuptsdk_easy_derive_key.
|
||||||
* Profile 1 is the same derivation with the descriptor made explicit so
|
* Profile 1 is the same derivation with the descriptor made explicit so
|
||||||
* future profiles (should the cost change) get distinct IDs. */
|
* future profiles (should the cost change) get distinct IDs. */
|
||||||
#define ZUPT_ARGON2_PROFILE_LEGACY 0x00 /* implicit: pre-3.4.0, no descriptor byte */
|
#define ZUPT_ARGON2_PROFILE_LEGACY 0x00 /* implicit: pre-3.4.0, no descriptor byte */
|
||||||
#define ZUPT_ARGON2_PROFILE_MODERATE 0x01 /* explicit: libzuptsdk MODERATE preset */
|
#define ZUPT_ARGON2_PROFILE_MODERATE 0x01 /* explicit: libvuptsdk MODERATE preset */
|
||||||
#define ZUPT_ARGON2_HDR_LEN_V1 33 /* [type|salt16|nonce16] */
|
#define ZUPT_ARGON2_HDR_LEN_V1 33 /* [type|salt16|nonce16] */
|
||||||
#define ZUPT_ARGON2_HDR_LEN_V2 34 /* + [profile1] */
|
#define ZUPT_ARGON2_HDR_LEN_V2 34 /* + [profile1] */
|
||||||
|
|
||||||
|
|
@ -136,7 +213,7 @@
|
||||||
#define ZUPT_BLOCK_DATA 0x00
|
#define ZUPT_BLOCK_DATA 0x00
|
||||||
#define ZUPT_BLOCK_INDEX 0x02
|
#define ZUPT_BLOCK_INDEX 0x02
|
||||||
#define ZUPT_BLOCK_ENC_HEADER 0x03
|
#define ZUPT_BLOCK_ENC_HEADER 0x03
|
||||||
#define ZUPT_BLOCK_DEDUP_REF 0x04 /* Dedup reference: payload = 8B offset of original block */
|
#define ZUPT_BLOCK_DEDUP_REF 0x04 /* Dedup reference; authenticated v5.2.2 payload also carries source AAD sequence */
|
||||||
#define ZUPT_BLOCK_COMMENT 0x05 /* v2.4.3: free-form UTF-8 comment, encrypted same as data blocks */
|
#define ZUPT_BLOCK_COMMENT 0x05 /* v2.4.3: free-form UTF-8 comment, encrypted same as data blocks */
|
||||||
|
|
||||||
#define ZUPT_MAX_COMMENT_LEN 4096 /* Maximum comment payload size (bytes). */
|
#define ZUPT_MAX_COMMENT_LEN 4096 /* Maximum comment payload size (bytes). */
|
||||||
|
|
@ -270,7 +347,7 @@ typedef struct {
|
||||||
int level; uint32_t block_size; uint16_t codec_id;
|
int level; uint32_t block_size; uint16_t codec_id;
|
||||||
int verbose, encrypt, quiet, solid, threads;
|
int verbose, encrypt, quiet, solid, threads;
|
||||||
int pq_mode; /* 1 = post-quantum hybrid KEM mode */
|
int pq_mode; /* 1 = post-quantum hybrid KEM mode */
|
||||||
int sdk_mode; /* 1 = use libzuptsdk-backed v3 crypto (HKDF combiner + commitment + HPKE) */
|
int sdk_mode; /* 1 = use libvuptsdk-backed v3 crypto (HKDF combiner + commitment + HPKE) */
|
||||||
int box_mode; /* 1 = libpqvaptvupt sealed-box mode (ZUPT_ENC_PQ_BOX_V1) */
|
int box_mode; /* 1 = libpqvaptvupt sealed-box mode (ZUPT_ENC_PQ_BOX_V1) */
|
||||||
int pqonly_mode; /* 1 = full post-quantum mode: ML-KEM-768 only (ZUPT_ENC_PQ_ONLY) */
|
int pqonly_mode; /* 1 = full post-quantum mode: ML-KEM-768 only (ZUPT_ENC_PQ_ONLY) */
|
||||||
int dedup; /* 1 = block-level deduplication enabled */
|
int dedup; /* 1 = block-level deduplication enabled */
|
||||||
|
|
@ -345,7 +422,7 @@ static inline void zupt_secure_wipe(void *ptr, size_t len) {
|
||||||
|
|
||||||
static inline int zupt_is_regular_file(const char *path) {
|
static inline int zupt_is_regular_file(const char *path) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
DWORD attr = GetFileAttributesA(path);
|
DWORD attr = zupt_win_get_attributes_utf8(path);
|
||||||
if (attr == INVALID_FILE_ATTRIBUTES) return 0;
|
if (attr == INVALID_FILE_ATTRIBUTES) return 0;
|
||||||
return !(attr & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE |
|
return !(attr & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE |
|
||||||
FILE_ATTRIBUTE_REPARSE_POINT));
|
FILE_ATTRIBUTE_REPARSE_POINT));
|
||||||
|
|
@ -396,10 +473,11 @@ void zupt_hmac_sha256_init(zupt_hmac_ctx *c, const uint8_t *key, size_t klen);
|
||||||
void zupt_hmac_sha256_update(zupt_hmac_ctx *c, const uint8_t *data, size_t dlen);
|
void zupt_hmac_sha256_update(zupt_hmac_ctx *c, const uint8_t *data, size_t dlen);
|
||||||
void zupt_hmac_sha256_final(zupt_hmac_ctx *c, uint8_t mac[32]);
|
void zupt_hmac_sha256_final(zupt_hmac_ctx *c, uint8_t mac[32]);
|
||||||
|
|
||||||
/* Constant-time buffer equality. Returns 1 if equal, 0 otherwise, in
|
/* Constant-time-intended buffer equality. Returns 1 if equal, 0 otherwise.
|
||||||
* time dependent only on n (not contents / mismatch position). The single
|
* The source has a fixed-length OR-accumulate loop without an intended
|
||||||
* audited MAC-tag comparison primitive; timing-verified by the
|
* content-dependent exit or access. The dudect-style regression in
|
||||||
* dudect-style test in tests/test_ct_timing.c. CT-REQUIRED. */
|
* tests/test_ct_timing.c measures exact builds when its control is conclusive;
|
||||||
|
* it is not a formal guarantee about compiler output. CT-REQUIRED. */
|
||||||
int zupt_ct_memeq(const void *a, const void *b, size_t n);
|
int zupt_ct_memeq(const void *a, const void *b, size_t n);
|
||||||
void zupt_pbkdf2_sha256(const uint8_t *pw, size_t pwlen, const uint8_t *salt, size_t slen, uint32_t iter, uint8_t *out, size_t olen);
|
void zupt_pbkdf2_sha256(const uint8_t *pw, size_t pwlen, const uint8_t *salt, size_t slen, uint32_t iter, uint8_t *out, size_t olen);
|
||||||
void zupt_aes256_ctr(const uint8_t key[32], const uint8_t nonce[16], const uint8_t *in, uint8_t *out, size_t len);
|
void zupt_aes256_ctr(const uint8_t key[32], const uint8_t nonce[16], const uint8_t *in, uint8_t *out, size_t len);
|
||||||
|
|
@ -477,8 +555,20 @@ zupt_error_t zupt_compress_files(const char *out, const char **arc, const char *
|
||||||
zupt_error_t zupt_extract_archive(const char *arc, const char *dir, zupt_options_t *opts);
|
zupt_error_t zupt_extract_archive(const char *arc, const char *dir, zupt_options_t *opts);
|
||||||
zupt_error_t zupt_list_archive(const char *arc, zupt_options_t *opts);
|
zupt_error_t zupt_list_archive(const char *arc, zupt_options_t *opts);
|
||||||
zupt_error_t zupt_test_archive(const char *arc, zupt_options_t *opts);
|
zupt_error_t zupt_test_archive(const char *arc, zupt_options_t *opts);
|
||||||
|
/* Internal stream variant used when a caller has pinned a private snapshot.
|
||||||
|
* It never closes stream; the caller retains ownership. */
|
||||||
|
zupt_error_t zupt_test_archive_stream(FILE *stream, zupt_options_t *opts);
|
||||||
|
zupt_error_t zupt_open_archive_internal(FILE *stream, zupt_options_t *opts,
|
||||||
|
zupt_archive_header_t *header,
|
||||||
|
zupt_footer_t *footer,
|
||||||
|
zupt_index_entry_t **entries,
|
||||||
|
int *num_entries);
|
||||||
|
|
||||||
/* ─── Hybrid PQ KEM (ML-KEM-768 + X25519) ─── */
|
/* ─── Hybrid PQ KEM (ML-KEM-768 + X25519) ─── */
|
||||||
|
/* Internal no-replace writer shared by the native and optional pq-box key
|
||||||
|
* formats. Private material receives platform-specific restrictive access. */
|
||||||
|
int zupt_keyfile_write_new(const char *path, const uint8_t *data, size_t length,
|
||||||
|
int private_material);
|
||||||
int zupt_hybrid_keygen(const char *keyfile);
|
int zupt_hybrid_keygen(const char *keyfile);
|
||||||
int zupt_hybrid_export_pubkey(const char *privfile, const char *pubfile);
|
int zupt_hybrid_export_pubkey(const char *privfile, const char *pubfile);
|
||||||
int zupt_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
|
int zupt_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
|
||||||
|
|
@ -494,14 +584,14 @@ int zupt_pq_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
|
||||||
int zupt_pq_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
|
int zupt_pq_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
|
||||||
const uint8_t *enc_hdr, size_t enc_hdr_len);
|
const uint8_t *enc_hdr, size_t enc_hdr_len);
|
||||||
|
|
||||||
/* ─── SDK-backed crypto (zupt v2.2+, libzuptsdk under the hood) ─── */
|
/* ─── SDK-backed crypto (zupt v2.2+, optional libvuptsdk) ─── */
|
||||||
int zupt_sdk_hybrid_keygen(const char *privkeyfile, const char *pubkeyfile);
|
int zupt_sdk_hybrid_keygen(const char *privkeyfile, const char *pubkeyfile);
|
||||||
int zupt_sdk_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
|
int zupt_sdk_hybrid_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
|
||||||
uint8_t *enc_hdr, size_t *enc_hdr_len);
|
uint8_t *enc_hdr, size_t *enc_hdr_len);
|
||||||
int zupt_sdk_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
|
int zupt_sdk_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
|
||||||
const uint8_t *enc_hdr, size_t enc_hdr_len);
|
const uint8_t *enc_hdr, size_t enc_hdr_len);
|
||||||
|
|
||||||
/* pq-box mode (ZUPT_ENC_PQ_BOX_V1, vendored libpqvaptvupt) */
|
/* pq-box mode (ZUPT_ENC_PQ_BOX_V1, optional system libpqvaptvupt) */
|
||||||
int zupt_pqbox_keygen(const char *privkeyfile, const char *pubkeyfile);
|
int zupt_pqbox_keygen(const char *privkeyfile, const char *pubkeyfile);
|
||||||
int zupt_pqbox_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
|
int zupt_pqbox_encrypt_init(zupt_keyring_t *kr, const char *pubkeyfile,
|
||||||
uint8_t *enc_hdr, size_t *enc_hdr_len);
|
uint8_t *enc_hdr, size_t *enc_hdr_len);
|
||||||
|
|
@ -519,7 +609,7 @@ void zupt_format_size(uint64_t bytes, char *buf, size_t cap);
|
||||||
|
|
||||||
/* Resolve ZUPT_CODEC_AUTO to a concrete codec based on hardware.
|
/* Resolve ZUPT_CODEC_AUTO to a concrete codec based on hardware.
|
||||||
* On x86_64 with AVX2: VaptVupt (fast ANS+SIMD decode).
|
* On x86_64 with AVX2: VaptVupt (fast ANS+SIMD decode).
|
||||||
* On all other arches: Zupt-LZHP (no SIMD dependency).
|
* On all other arches: ZUPT-LZHP (no SIMD dependency).
|
||||||
* Decompression of ALL codecs works on ALL architectures. */
|
* Decompression of ALL codecs works on ALL architectures. */
|
||||||
uint16_t zupt_resolve_auto_codec(void);
|
uint16_t zupt_resolve_auto_codec(void);
|
||||||
|
|
||||||
|
|
@ -538,18 +628,58 @@ zupt_error_t zupt_disk_restore(const char *archive_path, const char *target_path
|
||||||
zupt_options_t *opts);
|
zupt_options_t *opts);
|
||||||
|
|
||||||
/* ─── Internal Block I/O (used by format + disk modules) ─── */
|
/* ─── Internal Block I/O (used by format + disk modules) ─── */
|
||||||
|
typedef struct zupt_atomic_output zupt_atomic_output_t;
|
||||||
|
|
||||||
|
/* Create an archive in a private file next to OUTPUT_PATH. finish(..., 1)
|
||||||
|
* atomically replaces only the final directory entry; it never follows a
|
||||||
|
* symlink/reparse point at the leaf. finish(..., 0) removes the temporary. */
|
||||||
|
zupt_atomic_output_t *zupt_atomic_output_open(const char *output_path,
|
||||||
|
FILE **stream_out);
|
||||||
|
int zupt_atomic_output_finish(zupt_atomic_output_t *output, int publish);
|
||||||
|
|
||||||
zupt_error_t read_block(FILE *f, zupt_block_t *b);
|
zupt_error_t read_block(FILE *f, zupt_block_t *b);
|
||||||
zupt_error_t read_enc_header(FILE *f, zupt_archive_header_t *hdr, zupt_options_t *opts);
|
zupt_error_t read_enc_header(FILE *f, zupt_archive_header_t *hdr, zupt_options_t *opts);
|
||||||
zupt_error_t decompress_block(const zupt_block_t *b, const zupt_keyring_t *kr,
|
zupt_error_t decompress_block(const zupt_block_t *b, const zupt_keyring_t *kr,
|
||||||
uint64_t block_seq, uint8_t **out, size_t *olen);
|
uint64_t block_seq, uint8_t **out, size_t *olen);
|
||||||
|
|
||||||
|
/* Published 5.2.1 encrypted+dedup disk images bound DATA authentication to
|
||||||
|
* each frame's linear sequence, while legacy references stored only offsets.
|
||||||
|
* Readers build this private offset-to-sequence map before restoring them. */
|
||||||
|
typedef struct {
|
||||||
|
uint64_t offset;
|
||||||
|
uint64_t aad_seq;
|
||||||
|
} zupt_legacy_disk_aad_entry_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
zupt_legacy_disk_aad_entry_t *entries;
|
||||||
|
size_t count;
|
||||||
|
size_t capacity;
|
||||||
|
} zupt_legacy_disk_aad_map_t;
|
||||||
|
|
||||||
|
zupt_error_t zupt_legacy_disk_aad_map_build(
|
||||||
|
FILE *stream, uint64_t first_block_offset, uint32_t block_count,
|
||||||
|
zupt_legacy_disk_aad_map_t *map);
|
||||||
|
int zupt_legacy_disk_aad_map_lookup(
|
||||||
|
const zupt_legacy_disk_aad_map_t *map, uint64_t offset,
|
||||||
|
uint64_t *aad_seq);
|
||||||
|
void zupt_legacy_disk_aad_map_free(zupt_legacy_disk_aad_map_t *map);
|
||||||
|
|
||||||
zupt_error_t write_enc_header(FILE *out, zupt_archive_header_t *hdr,
|
zupt_error_t write_enc_header(FILE *out, zupt_archive_header_t *hdr,
|
||||||
zupt_options_t *opts);
|
zupt_options_t *opts);
|
||||||
int zupt_w8(FILE *f, uint8_t v);
|
int zupt_w8(FILE *f, uint8_t v);
|
||||||
int zupt_w16le(FILE *f, uint16_t v);
|
int zupt_w16le(FILE *f, uint16_t v);
|
||||||
int zupt_w64le(FILE *f, uint64_t v);
|
int zupt_w64le(FILE *f, uint64_t v);
|
||||||
|
void zupt_serialize_archive_header(const zupt_archive_header_t *header,
|
||||||
|
uint8_t out[ZUPT_ARCHIVE_HEADER_SIZE]);
|
||||||
|
void zupt_serialize_footer(const zupt_footer_t *footer,
|
||||||
|
uint8_t out[ZUPT_FOOTER_SIZE]);
|
||||||
|
int zupt_write_archive_header(FILE *stream,
|
||||||
|
const zupt_archive_header_t *header);
|
||||||
|
int zupt_write_footer(FILE *stream, const zupt_footer_t *footer);
|
||||||
|
|
||||||
/* ─── Block-Level Deduplication ─── */
|
/* ─── Block-Level Deduplication ─── */
|
||||||
#define ZUPT_DEDUP_MAX_ENTRIES (2 * 1024 * 1024) /* 2M entries, ~48MB RAM */
|
#define ZUPT_DEDUP_MAX_ENTRIES (2 * 1024 * 1024) /* 2M entries, ~80MB RAM */
|
||||||
|
#define ZUPT_DEDUP_DIGEST_SIZE 16 /* SHA-256 prefix paired with XXH64 */
|
||||||
|
|
||||||
typedef struct zupt_dedup_ctx zupt_dedup_ctx_t;
|
typedef struct zupt_dedup_ctx zupt_dedup_ctx_t;
|
||||||
|
|
||||||
|
|
@ -566,6 +696,17 @@ void zupt_dedup_stats(const zupt_dedup_ctx_t *ctx,
|
||||||
uint64_t *bytes_saved);
|
uint64_t *bytes_saved);
|
||||||
int zupt_dedup_write_ref(FILE *out, uint64_t ref_offset,
|
int zupt_dedup_write_ref(FILE *out, uint64_t ref_offset,
|
||||||
uint32_t orig_size, uint64_t orig_checksum);
|
uint32_t orig_size, uint64_t orig_checksum);
|
||||||
|
int zupt_dedup_write_ref_secure(FILE *out, uint64_t ref_offset,
|
||||||
|
uint32_t orig_size, uint64_t orig_checksum,
|
||||||
|
uint64_t current_aad_seq,
|
||||||
|
uint64_t referenced_aad_seq,
|
||||||
|
const zupt_keyring_t *keyring);
|
||||||
|
zupt_error_t zupt_dedup_read_ref(const zupt_block_t *block,
|
||||||
|
const zupt_keyring_t *keyring,
|
||||||
|
int require_authentication,
|
||||||
|
uint64_t current_aad_seq,
|
||||||
|
uint64_t *ref_offset,
|
||||||
|
uint64_t *referenced_aad_seq);
|
||||||
|
|
||||||
/* ─── Archive Info (read-only metadata inspection) ─── */
|
/* ─── Archive Info (read-only metadata inspection) ─── */
|
||||||
zupt_error_t zupt_archive_info(const char *path);
|
zupt_error_t zupt_archive_info(const char *path);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
* Copyright (c) 2025-2026 Cristian Cezar Moisés
|
* Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
* Zupt — ACSL Custom Predicates for Frama-C/WP
|
* ZUPT — ACSL Custom Predicates for Frama-C/WP
|
||||||
* Copyright (c) 2026 Cristian Cezar Moisés — AGPL-3.0-or-later
|
* Copyright (c) 2026 Cristian Cezar Moisés — AGPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* Usage: frama-c -wp -wp-rte -wp-model Typed+Cast
|
* Usage: frama-c -wp -wp-rte -wp-model Typed+Cast
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
* Copyright (c) 2025-2026 Cristian Cezar Moisés
|
* Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
* Zupt — CPU Feature Detection
|
* ZUPT — CPU Feature Detection
|
||||||
* Copyright (c) 2026 Cristian Cezar Moisés — AGPL-3.0-or-later
|
* Copyright (c) 2026 Cristian Cezar Moisés — AGPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
#ifndef ZUPT_CPUID_H
|
#ifndef ZUPT_CPUID_H
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
* Copyright (c) 2025-2026 Cristian Cezar Moisés
|
* Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
* Zupt — Jasmin Verified Crypto Declarations
|
* ZUPT — optional x86_64 crypto assembly declarations
|
||||||
* Copyright (c) 2026 Cristian Cezar Moisés — AGPL-3.0-or-later
|
* Copyright (c) 2026 Cristian Cezar Moisés — AGPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* Extern declarations for Jasmin-compiled assembly functions.
|
* Four declarations below correspond to checked-in jasminc output. The
|
||||||
* These replace C fallbacks when built with -DZUPT_USE_JASMIN.
|
* zupt_aes256_ctr4 implementation is separately identified hand-written
|
||||||
|
* assembly matching an algorithm-only .jazz description. These functions
|
||||||
|
* replace C fallbacks when built with -DZUPT_USE_JASMIN.
|
||||||
*
|
*
|
||||||
* Calling convention: System V AMD64 ABI.
|
* Calling convention: System V AMD64 ABI.
|
||||||
* Pointer args passed in RDI, RSI, RDX, RCX, R8, R9.
|
* Pointer args passed in RDI, RSI, RDX, RCX, R8, R9.
|
||||||
*
|
*
|
||||||
* v2.0.0: All 4 Jasmin functions wired and active.
|
* All five optional declarations are wired when the feature is enabled.
|
||||||
*/
|
*/
|
||||||
#ifndef ZUPT_JASMIN_H
|
#ifndef ZUPT_JASMIN_H
|
||||||
#define ZUPT_JASMIN_H
|
#define ZUPT_JASMIN_H
|
||||||
|
|
@ -18,24 +20,25 @@
|
||||||
#ifdef ZUPT_USE_JASMIN
|
#ifdef ZUPT_USE_JASMIN
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
/* JASMIN-VERIFIED: CT MAC comparison (4×u64 XOR accumulation).
|
/* JASMIN PATH: CT-intended MAC comparison (4×u64 XOR accumulation).
|
||||||
* Returns 0 if all 32 bytes match, nonzero if any differ.
|
* Returns 0 if all 32 bytes match, nonzero if any differ.
|
||||||
* Replaces XOR loop in zupt_decrypt_buffer(). */
|
* Replaces XOR loop in zupt_decrypt_buffer(). */
|
||||||
extern uint64_t zupt_mac_verify_ct(const void *expected, const void *actual);
|
extern uint64_t zupt_mac_verify_ct(const void *expected, const void *actual);
|
||||||
|
|
||||||
/* JASMIN-VERIFIED: CT conditional select (4×u64 masked select).
|
/* JASMIN PATH: CT-intended conditional select (4×u64 masked select).
|
||||||
* if cond==0: copies a→out. if cond!=0: copies b→out.
|
* if cond==0: copies a→out. if cond!=0: copies b→out.
|
||||||
* Replaces cmov in zupt_mlkem768_decaps(). */
|
* Replaces cmov in zupt_mlkem768_decaps(). */
|
||||||
extern void zupt_ct_select_32(void *out, const void *a,
|
extern void zupt_ct_select_32(void *out, const void *a,
|
||||||
const void *b, uint64_t cond);
|
const void *b, uint64_t cond);
|
||||||
|
|
||||||
/* JASMIN-VERIFIED: CT conditional swap (4×u64 masked XOR swap).
|
/* JASMIN PATH: CT-intended conditional swap (4×u64 masked XOR swap).
|
||||||
* if cond==0: no-op. if cond==1: swaps a↔b in place.
|
* if cond==0: no-op. if cond==1: swaps a↔b in place.
|
||||||
* Replaces fe_cswap in zupt_x25519.c.
|
* Replaces fe_cswap in zupt_x25519.c.
|
||||||
* NOTE: Requires 4×u64 field element layout (donna64). */
|
* Operates on exactly four consecutive u64 values; the X25519 caller handles
|
||||||
|
* its fifth 51-bit limb separately. */
|
||||||
extern void zupt_fe_cswap(void *a, void *b, uint64_t cond);
|
extern void zupt_fe_cswap(void *a, void *b, uint64_t cond);
|
||||||
|
|
||||||
/* JASMIN-VERIFIED: AES-256 single-block encrypt via AES-NI.
|
/* JASMIN PATH: AES-256 single-block encrypt via AES-NI.
|
||||||
* out = AES-256-ECB(key, ctr) XOR in.
|
* out = AES-256-ECB(key, ctr) XOR in.
|
||||||
* FIX v2.0.0: Stack offset bug resolved — round keys at correct
|
* FIX v2.0.0: Stack offset bug resolved — round keys at correct
|
||||||
* 16-byte aligned offsets. Requires AES-NI (checked via CPUID).
|
* 16-byte aligned offsets. Requires AES-NI (checked via CPUID).
|
||||||
|
|
@ -49,7 +52,7 @@ extern void zupt_fe_cswap(void *a, void *b, uint64_t cond);
|
||||||
extern void zupt_aes256_blk(void *out, const void *in,
|
extern void zupt_aes256_blk(void *out, const void *in,
|
||||||
const void *key, const void *ctr);
|
const void *key, const void *ctr);
|
||||||
|
|
||||||
/* JASMIN-VERIFIED: AES-256-CTR 4-block pipeline via AES-NI.
|
/* HAND-WRITTEN ASSEMBLY PATH: AES-256-CTR 4-block pipeline via AES-NI.
|
||||||
* Processes nblocks×16 bytes with 4-way interleaving.
|
* Processes nblocks×16 bytes with 4-way interleaving.
|
||||||
* Counter is updated in-place (big-endian increment in bytes [8..15]).
|
* Counter is updated in-place (big-endian increment in bytes [8..15]).
|
||||||
* Requires AES-NI. Falls back to zupt_aes256_blk for remaining 1-3 blocks.
|
* Requires AES-NI. Falls back to zupt_aes256_blk for remaining 1-3 blocks.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Zupt — Backup-oriented compression with AES-256 encryption
|
* ZUPT — Backup-oriented compression with AES-256 encryption
|
||||||
* Copyright (c) 2026 Cristian Cezar Moisés
|
* Copyright (c) 2026 Cristian Cezar Moisés
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Zupt — Backup-oriented compression with AES-256 encryption
|
* ZUPT — Backup-oriented compression with AES-256 encryption
|
||||||
* Copyright (c) 2026 Cristian Cezar Moisés
|
* Copyright (c) 2026 Cristian Cezar Moisés
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
*
|
*
|
||||||
|
|
@ -64,7 +64,7 @@ int zupt_mlkem768_decaps(uint8_t ss[MLKEM_SSBYTES],
|
||||||
|
|
||||||
/* Self-test: NTT/iNTT roundtrip + CBD-sampler range invariants.
|
/* Self-test: NTT/iNTT roundtrip + CBD-sampler range invariants.
|
||||||
* Returns 1 on pass, 0 on fail. Called from test_vectors.c case 14
|
* Returns 1 on pass, 0 on fail. Called from test_vectors.c case 14
|
||||||
* (F-04, Zupt 2.2.4). */
|
* (F-04, ZUPT 2.2.4). */
|
||||||
int zupt_mlkem768_selftest(void);
|
int zupt_mlkem768_selftest(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* Zupt — Backup-oriented compression with AES-256 encryption
|
* ZUPT — Backup-oriented compression with AES-256 encryption
|
||||||
* Copyright (c) 2026 Cristian Cezar Moisés
|
* Copyright (c) 2026 Cristian Cezar Moisés
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* X25519 Diffie-Hellman key agreement (RFC 7748).
|
* X25519 Diffie-Hellman key agreement (RFC 7748).
|
||||||
* Montgomery ladder — constant-time by construction.
|
* Fixed-iteration Montgomery ladder, designed without secret-dependent
|
||||||
|
* branches or table lookups; compiled timing remains platform-dependent.
|
||||||
*/
|
*/
|
||||||
#ifndef ZUPT_X25519_H
|
#ifndef ZUPT_X25519_H
|
||||||
#define ZUPT_X25519_H
|
#define ZUPT_X25519_H
|
||||||
|
|
@ -12,7 +13,8 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
/* X25519(scalar, point) → result. All inputs/outputs are 32 bytes.
|
/* X25519(scalar, point) → result. All inputs/outputs are 32 bytes.
|
||||||
* CT-REQUIRED: Montgomery ladder is inherently constant-time. */
|
* CT-REQUIRED: keep the ladder free of intended secret-dependent branches and
|
||||||
|
* memory access. This source-level property is not a compiled timing proof. */
|
||||||
void zupt_x25519(uint8_t out[32], const uint8_t scalar[32], const uint8_t point[32]);
|
void zupt_x25519(uint8_t out[32], const uint8_t scalar[32], const uint8_t point[32]);
|
||||||
|
|
||||||
/* X25519 with the standard basepoint (9).
|
/* X25519 with the standard basepoint (9).
|
||||||
|
|
|
||||||
34
install.sh
34
install.sh
|
|
@ -1,29 +1,33 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
# Fast Installer for VaptVupt - GNU/Linux
|
# Fast installer for ZUPT - GNU/Linux
|
||||||
|
|
||||||
set -e
|
set -Eeuo pipefail
|
||||||
|
umask 077
|
||||||
|
|
||||||
echo "🔧 Installing VaptVupt..."
|
VERSION=${VERSION:-5.2.2}
|
||||||
|
PREFIX=${PREFIX:-/usr/local}
|
||||||
|
|
||||||
|
echo "🔧 Installing ZUPT..."
|
||||||
|
|
||||||
# Create temporary directory
|
# Create temporary directory
|
||||||
TMP_DIR=$(mktemp -d)
|
TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/zupt-install.XXXXXXXX")
|
||||||
|
trap 'chmod -R u+rwX "$TMP_DIR" 2>/dev/null || true; rm -rf -- "$TMP_DIR"' EXIT HUP INT TERM
|
||||||
|
|
||||||
# Clone and build
|
# Clone and build
|
||||||
git clone https://git.securityops.co/cristiancmoises/vaptvupt.git "$TMP_DIR/vaptvupt"
|
git clone --depth 1 --branch "v$VERSION" \
|
||||||
cd "$TMP_DIR/vaptvupt"
|
https://github.com/cristiancmoises/zupt.git "$TMP_DIR/zupt"
|
||||||
|
cd "$TMP_DIR/zupt"
|
||||||
|
|
||||||
make clean
|
make clean
|
||||||
make
|
make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf 2)" \
|
||||||
|
WITH_SDK=0 WITH_PQBOX=0
|
||||||
|
make WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
|
|
||||||
# Install
|
# Install
|
||||||
sudo make install
|
sudo make PREFIX="$PREFIX" WITH_SDK=0 WITH_PQBOX=0 \
|
||||||
|
INSTALL_LEGACY_ALIAS=0 install
|
||||||
|
|
||||||
echo "✅ VaptVupt successfully installed to /usr/local/bin/vaptvupt"
|
echo "✅ ZUPT $VERSION successfully installed to $PREFIX/bin/zupt"
|
||||||
echo "🔒 You can now run: vaptvupt (legacy 'zupt' symlink also installed)"
|
echo "🔒 You can now run: zupt"
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
cd ~
|
|
||||||
rm -rf "$TMP_DIR"
|
|
||||||
echo "🧹 Cleanup completed"
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
/* Zupt — AES-256 Single Block Encrypt via AES-NI (Jasmin)
|
/* ZUPT — AES-256 Single Block Encrypt via AES-NI (Jasmin)
|
||||||
* Copyright (c) 2026 Cristian Cezar Moisés
|
* Copyright (c) 2026 Cristian Cezar Moisés
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* CT-REQUIRED: AES-NI has no data-dependent timing.
|
* CT-REQUIRED: designed without intended secret-dependent branches or memory
|
||||||
|
* access. Compiled and microarchitectural timing is not proven here.
|
||||||
*
|
*
|
||||||
* FIX v2.0.0: replaced `stack u128[15] rk` with 15 individual
|
* FIX v2.0.0: replaced `stack u128[15] rk` with 15 individual
|
||||||
* `stack u128` variables. The array form uses byte-offset indexing
|
* `stack u128` variables. The array form uses byte-offset indexing
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
/* Zupt — AES-256-CTR 4-Block Pipeline via AES-NI (Jasmin)
|
/* ZUPT — AES-256-CTR 4-Block Pipeline via AES-NI (Jasmin)
|
||||||
* Copyright (c) 2026 Cristian Cezar Moisés
|
* Copyright (c) 2026 Cristian Cezar Moisés
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* CT-REQUIRED: AES-NI has no data-dependent timing.
|
* CT-REQUIRED: designed without intended secret-dependent branches or memory
|
||||||
|
* access. Compiled and microarchitectural timing is not proven here.
|
||||||
*
|
*
|
||||||
* Interleaves 4 independent counter blocks through the AES round
|
* Interleaves 4 independent counter blocks through the AES round
|
||||||
* pipeline. AES-NI has 4-cycle latency, 1-cycle throughput — so
|
* pipeline. AES-NI has 4-cycle latency, 1-cycle throughput — so
|
||||||
* 4 independent blocks saturate the pipeline for ~4× throughput.
|
* 4 independent blocks saturate the pipeline for ~4× throughput.
|
||||||
*
|
*
|
||||||
* Expected: ~3.5 GB/s AES-256-CTR on modern x86-64 (Zen3/Alder Lake).
|
|
||||||
*
|
|
||||||
* Interface:
|
* Interface:
|
||||||
* zupt_aes256_ctr4(out, in, key, ctr, nblocks)
|
* zupt_aes256_ctr4(out, in, key, ctr, nblocks)
|
||||||
* Encrypts nblocks×16 bytes. Counter is incremented in the last 8 bytes
|
* Encrypts nblocks×16 bytes. Counter is incremented in the last 8 bytes
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2026 Cristian Cezar Moisés
|
# Copyright (c) 2026 Cristian Cezar Moisés
|
||||||
# Generated from jasmin/zupt_aes_ctr4.jazz by jasminc.
|
# Hand-written production assembly matching the algorithm documented in
|
||||||
|
# jasmin/zupt_aes_ctr4.jazz; this file is not jasminc output.
|
||||||
.intel_syntax noprefix
|
.intel_syntax noprefix
|
||||||
.text
|
.text
|
||||||
.p2align 5
|
.p2align 5
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* Zupt — Constant-Time MAC Comparison (Jasmin)
|
/* ZUPT — Constant-Time MAC Comparison (Jasmin)
|
||||||
* Copyright (c) 2026 Cristian Cezar Moisés
|
* Copyright (c) 2026 Cristian Cezar Moisés
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* Zupt — ML-KEM Constant-Time Select (Jasmin)
|
/* ZUPT — ML-KEM Constant-Time Select (Jasmin)
|
||||||
* Copyright (c) 2026 Cristian Cezar Moisés
|
* Copyright (c) 2026 Cristian Cezar Moisés
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
/* Zupt — X25519 Constant-Time Conditional Swap (Jasmin)
|
/* ZUPT — X25519 Constant-Time Conditional Swap (Jasmin)
|
||||||
* Copyright (c) 2026 Cristian Cezar Moisés
|
* Copyright (c) 2026 Cristian Cezar Moisés
|
||||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
*
|
*
|
||||||
* CT-REQUIRED: fe_cswap must not leak cond via timing.
|
* CT-REQUIRED: fe_cswap must not leak cond via timing.
|
||||||
* This is the only CT-critical field operation in X25519.
|
* This is the only CT-critical field operation in X25519.
|
||||||
* fe_add/fe_sub/fe_mul use C fallback (data-independent timing
|
* fe_add/fe_sub/fe_mul use the C fallback. No fixed-latency claim is made for
|
||||||
* on x86-64 — ADD/MUL have fixed latency).
|
* every compiler, x86-64 CPU, or resulting binary.
|
||||||
*
|
*
|
||||||
* 4 × u64 limbs, pure register operations, no intrinsics needed.
|
* 4 × u64 limbs, pure register operations, no intrinsics needed.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,60 @@
|
||||||
# Maintainer: Cristian Cezar Moisés <sac@securityops.co>
|
# Maintainer: Cristian Cezar Moisés <sac@securityops.co>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# AUR submission instructions:
|
# AUR submission instructions:
|
||||||
# 1. Run `make dist` upstream to produce zupt-VERSION.tar.gz.
|
# 1. Run `make dist` upstream to produce zupt-VERSION.tar.gz.
|
||||||
# 2. Upload that tarball somewhere stable (GitHub release / git.securityops.co).
|
# 2. Upload that tarball to the canonical GitHub release.
|
||||||
# 3. Update `source=()` URL and `sha256sums=()` below.
|
# 3. Update `source=()` URL and `sha256sums=()` below.
|
||||||
# 4. Run `makepkg --printsrcinfo > .SRCINFO` in this directory.
|
# 4. Run `makepkg --printsrcinfo > .SRCINFO` in this directory.
|
||||||
# 5. Commit and push to ssh://aur@aur.archlinux.org/zupt.git
|
# 5. Commit and push to the separately maintained AUR package repository.
|
||||||
#
|
#
|
||||||
# Test locally: `makepkg -s` in this directory after dropping a copy of the
|
# Test locally with `makepkg -s` after the release archive is published.
|
||||||
# zupt-VERSION.tar.gz alongside the PKGBUILD.
|
|
||||||
|
|
||||||
pkgname=vaptvupt
|
pkgname=zupt
|
||||||
pkgver=5.0.0
|
pkgver=5.2.2
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
provides=('zupt')
|
|
||||||
replaces=('zupt')
|
|
||||||
conflicts=('zupt')
|
|
||||||
pkgdesc='Pure-C11 post-quantum backup compression utility (AES-256-CTR + HMAC-SHA256 + ML-KEM-768 + X25519)'
|
pkgdesc='Pure-C11 post-quantum backup compression utility (AES-256-CTR + HMAC-SHA256 + ML-KEM-768 + X25519)'
|
||||||
arch=('x86_64' 'aarch64')
|
arch=('x86_64')
|
||||||
url='https://git.securityops.co/cristiancmoises/vaptvupt'
|
url='https://github.com/cristiancmoises/zupt'
|
||||||
license=('AGPL-3.0-or-later')
|
license=('AGPL-3.0-or-later AND GPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND CC0-1.0')
|
||||||
depends=('glibc')
|
depends=('glibc')
|
||||||
makedepends=('gcc')
|
makedepends=('gcc' 'git' 'make')
|
||||||
checkdepends=('python')
|
checkdepends=('python')
|
||||||
|
|
||||||
# Replace SHA256 placeholder with output of:
|
source=("${pkgname}-${pkgver}.tar.gz::https://github.com/cristiancmoises/zupt/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||||
# sha256sum /tmp/zupt-2.4.4.tar.gz
|
# Updated from the byte-reproducible upstream release archive before publishing.
|
||||||
source=("${pkgname}-${pkgver}.tar.gz::https://git.securityops.co/cristiancmoises/vaptvupt/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
sha256sums=('REPLACE_AFTER_FINAL_RELEASE_ARCHIVE_IS_BUILT')
|
||||||
sha256sums=('SKIP')
|
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${pkgname}-${pkgver}"
|
cd "${pkgname}-${pkgver}"
|
||||||
# Source-only build (WITH_SDK=0) with the project's strict warning set.
|
# Source-only build (WITH_SDK=0) with the project's strict warning set.
|
||||||
CFLAGS="${CFLAGS:--O2 -std=c11} -Wall -Wextra -Wpedantic" \
|
CFLAGS="${CFLAGS:--O2 -std=c11} -Wall -Wextra -Wpedantic" \
|
||||||
make WITH_SDK=0 -j"$(nproc)"
|
make WITH_SDK=0 WITH_PQBOX=0 -j"$(nproc)"
|
||||||
}
|
}
|
||||||
|
|
||||||
check() {
|
check() {
|
||||||
cd "${pkgname}-${pkgver}"
|
cd "${pkgname}-${pkgver}"
|
||||||
# Distro-safe regression subset (F-06..F-12, dedup-nonce, NIST/RFC vectors).
|
# Distro-safe quick, traversal, integrity, codec and NIST/RFC checks.
|
||||||
make WITH_SDK=0 check
|
make WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
}
|
}
|
||||||
|
|
||||||
package() {
|
package() {
|
||||||
cd "${pkgname}-${pkgver}"
|
cd "${pkgname}-${pkgver}"
|
||||||
# Source-only build (no vendored libraries); `make install` places the
|
# Source-only build (no vendored libraries); `make install` places the
|
||||||
# binary, the zupt symlink, the man pages and the shell completions.
|
# binary, man page and shell completions under the public zupt name.
|
||||||
make DESTDIR="${pkgdir}" PREFIX=/usr WITH_SDK=0 install
|
make DESTDIR="${pkgdir}" PREFIX=/usr WITH_SDK=0 WITH_PQBOX=0 \
|
||||||
|
INSTALL_LEGACY_ALIAS=0 install
|
||||||
|
|
||||||
# Docs that aren't part of `make install`
|
# Docs that aren't part of `make install`
|
||||||
install -Dm644 README.md "${pkgdir}/usr/share/doc/${pkgname}/README.md"
|
install -Dm644 README.md "${pkgdir}/usr/share/doc/${pkgname}/README.md"
|
||||||
install -Dm644 SECURITY.md "${pkgdir}/usr/share/doc/${pkgname}/SECURITY.md"
|
install -Dm644 SECURITY.md "${pkgdir}/usr/share/doc/${pkgname}/SECURITY.md"
|
||||||
install -Dm644 CHANGELOG.md "${pkgdir}/usr/share/doc/${pkgname}/CHANGELOG.md"
|
install -Dm644 CHANGELOG.md "${pkgdir}/usr/share/doc/${pkgname}/CHANGELOG.md"
|
||||||
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
install -Dm644 LICENSE-AGPL-3.0 "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE-AGPL-3.0"
|
||||||
|
install -Dm644 LICENSE-GPL-3.0 "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE-GPL-3.0"
|
||||||
|
install -Dm644 LICENSE-BSD-2-Clause "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE-BSD-2-Clause"
|
||||||
|
install -Dm644 LICENSE-BSD-3-Clause "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE-BSD-3-Clause"
|
||||||
|
install -Dm644 LICENSE-CC0-1.0 "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE-CC0-1.0"
|
||||||
|
install -Dm644 NOTICE "${pkgdir}/usr/share/licenses/${pkgname}/NOTICE"
|
||||||
|
install -Dm644 THIRD-PARTY-NOTICES.md \
|
||||||
|
"${pkgdir}/usr/share/licenses/${pkgname}/THIRD-PARTY-NOTICES.md"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,165 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
# Build vaptvupt CLI as AppImage (portable single-file binary).
|
|
||||||
# Includes a legacy `zupt` symlink so AppDir users can invoke either name.
|
|
||||||
set -e
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
|
|
||||||
VERSION="${VERSION:-4.2.1}"
|
set -Eeuo pipefail
|
||||||
ARCH="${ARCH:-x86_64}"
|
|
||||||
PKGNAME="vaptvupt"
|
|
||||||
LEGACY="zupt"
|
|
||||||
NAME="$PKGNAME-$VERSION-$ARCH"
|
|
||||||
OUT="/tmp/${NAME}.AppDir"
|
|
||||||
|
|
||||||
rm -rf "$OUT"
|
umask 022
|
||||||
mkdir -p "$OUT/usr/bin" "$OUT/usr/share/applications" "$OUT/usr/share/icons/hicolor/256x256/apps"
|
export LC_ALL=C
|
||||||
|
|
||||||
# Source-only build: the binary links only libc/libm/pthread from the host,
|
die() {
|
||||||
# so the AppDir ships no bundled libraries.
|
printf 'FAIL: %s\n' "$*" >&2
|
||||||
install -m 755 $PKGNAME "$OUT/usr/bin/$PKGNAME"
|
exit 1
|
||||||
ln -sf $PKGNAME "$OUT/usr/bin/$LEGACY"
|
}
|
||||||
|
|
||||||
cat > "$OUT/AppRun" <<APPRUN
|
[[ $(uname -s) == Linux ]] || die 'AppImage packages must be built on Linux'
|
||||||
#!/bin/bash
|
|
||||||
HERE="\$(dirname "\$(readlink -f "\${0}")")"
|
|
||||||
export PATH="\$HERE/usr/bin:\$PATH"
|
|
||||||
exec "\$HERE/usr/bin/$PKGNAME" "\$@"
|
|
||||||
APPRUN
|
|
||||||
chmod +x "$OUT/AppRun"
|
|
||||||
|
|
||||||
cat > "$OUT/$PKGNAME.desktop" <<DESK
|
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||||
[Desktop Entry]
|
cd -- "$repo_root"
|
||||||
Name=VaptVupt
|
|
||||||
Comment=Post-quantum backup compression utility (formerly Zupt)
|
|
||||||
Exec=$PKGNAME
|
|
||||||
Terminal=true
|
|
||||||
Type=Application
|
|
||||||
Categories=Utility;Archiving;Security;
|
|
||||||
Icon=$PKGNAME
|
|
||||||
DESK
|
|
||||||
cp "$OUT/$PKGNAME.desktop" "$OUT/usr/share/applications/"
|
|
||||||
|
|
||||||
# 1x1 PNG placeholder — replace with a real icon when the brand asset exists
|
header_version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
printf '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\rIDATx\x9cc\xfc\xcf\xc0\x00\x00\x00\x05\x00\x01\xa5\xf6E@\x00\x00\x00\x00IEND\xaeB`\x82' > "$OUT/$PKGNAME.png"
|
version=${VERSION:-$header_version}
|
||||||
cp "$OUT/$PKGNAME.png" "$OUT/usr/share/icons/hicolor/256x256/apps/$PKGNAME.png"
|
[[ -n $version && $version == "$header_version" ]] || \
|
||||||
|
die "VERSION '$version' does not match include/zupt.h '$header_version'"
|
||||||
|
[[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "invalid package version: $version"
|
||||||
|
|
||||||
if command -v appimagetool >/dev/null 2>&1; then
|
case $(uname -m) in
|
||||||
ARCH=$ARCH appimagetool "$OUT" "/tmp/${NAME}.AppImage" 2>&1 | tail -5
|
x86_64|amd64) native_arch=x86_64 ;;
|
||||||
echo "Built: /tmp/${NAME}.AppImage"
|
aarch64|arm64) native_arch=aarch64 ;;
|
||||||
|
*) die "unsupported native AppImage architecture: $(uname -m)" ;;
|
||||||
|
esac
|
||||||
|
case ${ARCH:-$native_arch} in
|
||||||
|
x86_64|amd64) arch=x86_64 ;;
|
||||||
|
aarch64|arm64) arch=aarch64 ;;
|
||||||
|
*) die "unsupported AppImage architecture: ${ARCH:-$native_arch}" ;;
|
||||||
|
esac
|
||||||
|
[[ $arch == "$native_arch" ]] || \
|
||||||
|
die "ARCH=$arch does not match the native build architecture $native_arch"
|
||||||
|
|
||||||
|
appimagetool=${APPIMAGETOOL:-appimagetool}
|
||||||
|
if [[ $appimagetool == */* ]]; then
|
||||||
|
[[ -x $appimagetool ]] || die "APPIMAGETOOL is not executable: $appimagetool"
|
||||||
|
appimagetool=$(cd -- "$(dirname -- "$appimagetool")" && pwd -P)/$(basename -- "$appimagetool")
|
||||||
|
else
|
||||||
|
appimagetool=$(command -v -- "$appimagetool" || true)
|
||||||
|
[[ -n $appimagetool ]] || die 'appimagetool not found; set APPIMAGETOOL to a verified local executable'
|
||||||
|
fi
|
||||||
|
runtime_file=${APPIMAGE_RUNTIME_FILE:-}
|
||||||
|
[[ -n $runtime_file && -s $runtime_file ]] || \
|
||||||
|
die 'set APPIMAGE_RUNTIME_FILE to a locally verified type-2 runtime (network downloads are not performed)'
|
||||||
|
runtime_file=$(cd -- "$(dirname -- "$runtime_file")" && pwd -P)/$(basename -- "$runtime_file")
|
||||||
|
runtime_compliance_file=${APPIMAGE_RUNTIME_COMPLIANCE_FILE:-}
|
||||||
|
[[ -n $runtime_compliance_file && -s $runtime_compliance_file ]] || \
|
||||||
|
die 'set APPIMAGE_RUNTIME_COMPLIANCE_FILE to the audited runtime license/source-compliance notice'
|
||||||
|
runtime_compliance_file=$(cd -- "$(dirname -- "$runtime_compliance_file")" && pwd -P)/$(basename -- "$runtime_compliance_file")
|
||||||
|
|
||||||
|
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
|
||||||
|
mkdir -p -- "$dist_dir"
|
||||||
|
dist_dir=$(cd -- "$dist_dir" && pwd -P)
|
||||||
|
output=$dist_dir/zupt-${version}-linux-${arch}.AppImage
|
||||||
|
[[ ! -e $output ]] || die "refusing to overwrite existing output: $output"
|
||||||
|
|
||||||
|
for command_name in make readelf file sha256sum; do
|
||||||
|
command -v -- "$command_name" >/dev/null 2>&1 || die "required command not found: $command_name"
|
||||||
|
done
|
||||||
|
run_checks=${RUN_CHECKS:-1}
|
||||||
|
[[ $run_checks == 0 || $run_checks == 1 ]] || die 'RUN_CHECKS must be 0 or 1'
|
||||||
|
if [[ $run_checks == 1 ]]; then
|
||||||
|
command -v git >/dev/null 2>&1 || die 'git is required when RUN_CHECKS=1'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Always produce the AppDir tarball as well -- some environments (no FUSE,
|
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')}
|
||||||
# strict execve policies, etc.) cannot run the .AppImage directly. The
|
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-appimage.XXXXXXXX")
|
||||||
# tarball is the universal fallback: extract and run AppRun.
|
appdir=$work/ZUPT.AppDir
|
||||||
cd /tmp
|
image_tmp=$work/$(basename -- "$output")
|
||||||
tar -czf "${NAME}.AppDir.tar.gz" "$(basename "$OUT")"
|
|
||||||
echo "Built: /tmp/${NAME}.AppDir.tar.gz"
|
cleanup() {
|
||||||
echo "Users can run: tar xzf ${NAME}.AppDir.tar.gz && ./${NAME}.AppDir/AppRun version"
|
make -C "$repo_root" clean >/dev/null 2>&1 || true
|
||||||
|
chmod -R u+rwX "$work" 2>/dev/null || true
|
||||||
|
rm -rf -- "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
|
printf '[AppImage] source-only build of ZUPT %s (%s)\n' "$version" "$arch"
|
||||||
|
make clean
|
||||||
|
make -j"$jobs" V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
|
||||||
|
if [[ $run_checks == 1 ]]; then
|
||||||
|
make V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
|
||||||
|
fi
|
||||||
|
make DESTDIR="$appdir" PREFIX=/usr WITH_SDK=0 WITH_PQBOX=0 \
|
||||||
|
INSTALL_LEGACY_ALIAS=0 install
|
||||||
|
|
||||||
|
binary=$appdir/usr/bin/zupt
|
||||||
|
[[ -x $binary ]] || die 'AppDir executable is missing'
|
||||||
|
[[ ! -e $appdir/usr/bin/vaptvupt ]] || die 'legacy vaptvupt alias must not be packaged'
|
||||||
|
if readelf -d "$binary" 2>/dev/null | grep -Eq '(RPATH|RUNPATH)'; then
|
||||||
|
readelf -d "$binary" | grep -E '(RPATH|RUNPATH)' >&2
|
||||||
|
die 'AppDir executable contains RPATH/RUNPATH'
|
||||||
|
fi
|
||||||
|
if readelf -d "$binary" 2>/dev/null | grep -Eqi '(vendor/|libvuptsdk|libpqvaptvupt)'; then
|
||||||
|
die 'AppDir executable references a vendored optional library'
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p -- "$appdir/usr/share/applications" \
|
||||||
|
"$appdir/usr/share/doc/zupt" \
|
||||||
|
"$appdir/usr/share/icons/hicolor/128x128/apps" \
|
||||||
|
"$appdir/usr/share/licenses/zupt"
|
||||||
|
install -m 0644 README.md CHANGELOG.md SECURITY.md THREAT_MODEL.md \
|
||||||
|
"$appdir/usr/share/doc/zupt/"
|
||||||
|
install -m 0644 LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 \
|
||||||
|
LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE \
|
||||||
|
THIRD-PARTY-NOTICES.md "$appdir/usr/share/licenses/zupt/"
|
||||||
|
install -m 0644 gui/LICENSE-GUI \
|
||||||
|
"$appdir/usr/share/licenses/zupt/GUI-LICENSE.txt"
|
||||||
|
install -m 0644 gui/assets/README.md \
|
||||||
|
"$appdir/usr/share/licenses/zupt/GUI-ASSET-PROVENANCE.md"
|
||||||
|
install -m 0644 "$runtime_compliance_file" \
|
||||||
|
"$appdir/usr/share/licenses/zupt/AppImage-runtime-compliance.txt"
|
||||||
|
install -m 0644 gui/assets/zupt-128.png \
|
||||||
|
"$appdir/usr/share/icons/hicolor/128x128/apps/zupt.png"
|
||||||
|
cp -- "$appdir/usr/share/icons/hicolor/128x128/apps/zupt.png" "$appdir/zupt.png"
|
||||||
|
ln -s -- zupt.png "$appdir/.DirIcon"
|
||||||
|
|
||||||
|
desktop_file=dev.zupt.cli.desktop
|
||||||
|
cat > "$appdir/$desktop_file" <<'EOF'
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=ZUPT
|
||||||
|
Comment=Backup compression with authenticated and post-quantum encryption
|
||||||
|
Exec=zupt
|
||||||
|
Icon=zupt
|
||||||
|
Terminal=true
|
||||||
|
Categories=Utility;Archiving;
|
||||||
|
EOF
|
||||||
|
cp -- "$appdir/$desktop_file" "$appdir/usr/share/applications/$desktop_file"
|
||||||
|
|
||||||
|
cat > "$appdir/AppRun" <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
appdir=$(CDPATH= cd -P "$(dirname "$0")" && pwd -P)
|
||||||
|
exec "$appdir/usr/bin/zupt" "$@"
|
||||||
|
EOF
|
||||||
|
chmod 0755 "$appdir/AppRun"
|
||||||
|
|
||||||
|
forbidden=$(find "$appdir" -type f \( \
|
||||||
|
-name '*.o' -o -name '*.obj' -o -name '*.a' -o -name '*.so' -o \
|
||||||
|
-name '*.so.*' -o -name '*.dll' -o -name '*.dylib' \
|
||||||
|
\) -print)
|
||||||
|
[[ -z $forbidden ]] || {
|
||||||
|
printf '%s\n' "$forbidden" >&2
|
||||||
|
die 'compiled library or object found in AppDir'
|
||||||
|
}
|
||||||
|
|
||||||
|
bash scripts/test-installed-zupt.sh "$appdir/AppRun"
|
||||||
|
|
||||||
|
export ARCH=$arch
|
||||||
|
export VERSION=$version
|
||||||
|
export APPIMAGE_EXTRACT_AND_RUN=1
|
||||||
|
"$appimagetool" --runtime-file "$runtime_file" "$appdir" "$image_tmp"
|
||||||
|
chmod 0755 "$image_tmp"
|
||||||
|
file "$image_tmp" | grep -q 'ELF' || die 'generated AppImage does not have ELF magic'
|
||||||
|
bash scripts/test-installed-zupt.sh "$image_tmp"
|
||||||
|
|
||||||
|
mv -- "$image_tmp" "$output"
|
||||||
|
sha256sum "$output"
|
||||||
|
printf 'PASS: built and executed-package-tested %s\n' "$output"
|
||||||
|
|
|
||||||
|
|
@ -1,134 +1,139 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
#
|
|
||||||
# Build self-contained vaptvupt CLI .deb package.
|
|
||||||
#
|
|
||||||
# v3.0.0 rename: the binary is now `vaptvupt`; we install it at
|
|
||||||
# /usr/bin/vaptvupt and create /usr/bin/zupt → /usr/bin/vaptvupt as
|
|
||||||
# a legacy symlink for one major version cycle. The package name
|
|
||||||
# is `vaptvupt` with Provides/Replaces/Conflicts on `zupt` so
|
|
||||||
# `apt install zupt` still resolves cleanly.
|
|
||||||
#
|
|
||||||
# Bundles libzuptsdk.so.2 under /usr/lib/vaptvupt/ so users do NOT
|
|
||||||
# need to separately install the libzuptsdk package.
|
|
||||||
|
|
||||||
set -e
|
set -Eeuo pipefail
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
|
|
||||||
VERSION="${VERSION:-3.0.0}"
|
umask 022
|
||||||
ARCH="${ARCH:-amd64}"
|
export LC_ALL=C
|
||||||
PKGNAME="vaptvupt"
|
|
||||||
LEGACY="zupt"
|
|
||||||
|
|
||||||
PKG="${PKGNAME}_${VERSION}_${ARCH}"
|
die() {
|
||||||
ROOT="/tmp/$PKG"
|
printf 'FAIL: %s\n' "$*" >&2
|
||||||
|
|
||||||
# Vendored libzuptsdk path (relative to project root)
|
|
||||||
SDK_LIB="vendor/zuptsdk/libzuptsdk.so.2.0.0"
|
|
||||||
if [ ! -f "$SDK_LIB" ]; then
|
|
||||||
echo "ERROR: $SDK_LIB not found. Vendor the libzuptsdk shared object first." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
PQVV_LIB="vendor/pqvaptvupt/libpqvaptvupt.so.0.6.0"
|
|
||||||
if [ ! -f "$PQVV_LIB" ]; then
|
|
||||||
echo "ERROR: $PQVV_LIB not found. Vendor the libpqvaptvupt shared object first." >&2
|
|
||||||
exit 1
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||||
|
cd -- "$repo_root"
|
||||||
|
|
||||||
|
header_version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
|
version=${VERSION:-$header_version}
|
||||||
|
[[ -n $version && $version == "$header_version" ]] || \
|
||||||
|
die "VERSION '$version' does not match include/zupt.h '$header_version'"
|
||||||
|
[[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "invalid package version: $version"
|
||||||
|
|
||||||
|
native_arch=$(dpkg --print-architecture)
|
||||||
|
arch=${ARCH:-$native_arch}
|
||||||
|
[[ $arch == "$native_arch" ]] || \
|
||||||
|
die "ARCH=$arch does not match the native dpkg architecture $native_arch"
|
||||||
|
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
|
||||||
|
mkdir -p -- "$dist_dir"
|
||||||
|
dist_dir=$(cd -- "$dist_dir" && pwd -P)
|
||||||
|
output=$dist_dir/zupt_${version}_${arch}.deb
|
||||||
|
[[ ! -e $output ]] || die "refusing to overwrite existing output: $output"
|
||||||
|
|
||||||
|
for command_name in make dpkg dpkg-deb readelf sha256sum; do
|
||||||
|
command -v -- "$command_name" >/dev/null 2>&1 || die "required command not found: $command_name"
|
||||||
|
done
|
||||||
|
run_checks=${RUN_CHECKS:-1}
|
||||||
|
[[ $run_checks == 0 || $run_checks == 1 ]] || die 'RUN_CHECKS must be 0 or 1'
|
||||||
|
if [[ $run_checks == 1 ]]; then
|
||||||
|
command -v git >/dev/null 2>&1 || die 'git is required when RUN_CHECKS=1'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[deb] Building vaptvupt"
|
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')}
|
||||||
make clean >/dev/null 2>&1 || true
|
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-deb.XXXXXXXX")
|
||||||
make -j"$(nproc)" >/dev/null
|
stage=$work/stage
|
||||||
|
extract=$work/extract
|
||||||
|
|
||||||
echo "[deb] Patching rpath -> /usr/lib/$PKGNAME:/usr/lib64/$PKGNAME"
|
cleanup() {
|
||||||
patchelf --set-rpath "/usr/lib/$PKGNAME:/usr/lib64/$PKGNAME" $PKGNAME
|
make -C "$repo_root" clean >/dev/null 2>&1 || true
|
||||||
|
chmod -R u+rwX "$work" 2>/dev/null || true
|
||||||
|
rm -rf -- "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
if ! readelf -d $PKGNAME | grep -q "RUNPATH.*\[/usr/lib/$PKGNAME:/usr/lib64/$PKGNAME\]"; then
|
printf '[deb] source-only build of ZUPT %s (%s)\n' "$version" "$arch"
|
||||||
echo "ERROR: built $PKGNAME does not have correct RUNPATH" >&2
|
make clean
|
||||||
readelf -d $PKGNAME | grep -E "RPATH|RUNPATH"
|
make -j"$jobs" V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
|
||||||
exit 1
|
if [[ $run_checks == 1 ]]; then
|
||||||
|
make V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -rf "$ROOT"
|
make DESTDIR="$stage" PREFIX=/usr WITH_SDK=0 WITH_PQBOX=0 \
|
||||||
mkdir -p "$ROOT/DEBIAN" \
|
INSTALL_LEGACY_ALIAS=0 install
|
||||||
"$ROOT/usr/bin" \
|
|
||||||
"$ROOT/usr/lib/$PKGNAME" \
|
|
||||||
"$ROOT/usr/share/doc/$PKGNAME" \
|
|
||||||
"$ROOT/usr/share/man/man1" \
|
|
||||||
"$ROOT/usr/share/bash-completion/completions" \
|
|
||||||
"$ROOT/usr/share/zsh/site-functions" \
|
|
||||||
"$ROOT/usr/share/fish/vendor_completions.d"
|
|
||||||
|
|
||||||
# Binary + legacy symlink
|
binary=$stage/usr/bin/zupt
|
||||||
install -m 755 $PKGNAME "$ROOT/usr/bin/$PKGNAME"
|
[[ -x $binary ]] || die 'staged /usr/bin/zupt is missing'
|
||||||
ln -sf $PKGNAME "$ROOT/usr/bin/$LEGACY"
|
[[ ! -e $stage/usr/bin/vaptvupt ]] || die 'legacy /usr/bin/vaptvupt must not be packaged'
|
||||||
|
|
||||||
# Bundled libzuptsdk
|
if readelf -d "$binary" 2>/dev/null | grep -Eq '(RPATH|RUNPATH)'; then
|
||||||
install -m 755 "$SDK_LIB" "$ROOT/usr/lib/$PKGNAME/libzuptsdk.so.2.0.0"
|
readelf -d "$binary" | grep -E '(RPATH|RUNPATH)' >&2
|
||||||
ln -sf libzuptsdk.so.2.0.0 "$ROOT/usr/lib/$PKGNAME/libzuptsdk.so.2"
|
die 'staged executable contains RPATH/RUNPATH'
|
||||||
ln -sf libzuptsdk.so.2.0.0 "$ROOT/usr/lib/$PKGNAME/libzuptsdk.so"
|
fi
|
||||||
install -m 755 "$PQVV_LIB" "$ROOT/usr/lib/$PKGNAME/libpqvaptvupt.so.0.6.0"
|
if readelf -d "$binary" 2>/dev/null | grep -Eqi '(vendor/|libvuptsdk|libpqvaptvupt)'; then
|
||||||
ln -sf libpqvaptvupt.so.0.6.0 "$ROOT/usr/lib/$PKGNAME/libpqvaptvupt.so.0"
|
die 'staged executable references a vendored optional library'
|
||||||
ln -sf libpqvaptvupt.so.0.6.0 "$ROOT/usr/lib/$PKGNAME/libpqvaptvupt.so"
|
|
||||||
|
|
||||||
# Manpage (gzip-compressed); install + legacy alias
|
|
||||||
if [ -f doc/vaptvupt.1 ]; then
|
|
||||||
gzip -9n -c doc/vaptvupt.1 > "$ROOT/usr/share/man/man1/$PKGNAME.1.gz"
|
|
||||||
ln -sf $PKGNAME.1.gz "$ROOT/usr/share/man/man1/$LEGACY.1.gz"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Shell completions
|
forbidden=$(find "$stage" -type f \( \
|
||||||
if [ -f completions/vaptvupt.bash ]; then
|
-name '*.o' -o -name '*.obj' -o -name '*.a' -o -name '*.so' -o \
|
||||||
install -m 0644 completions/vaptvupt.bash "$ROOT/usr/share/bash-completion/completions/$PKGNAME"
|
-name '*.so.*' -o -name '*.dll' -o -name '*.dylib' -o -name '*.exe' \
|
||||||
ln -sf $PKGNAME "$ROOT/usr/share/bash-completion/completions/$LEGACY"
|
\) -print)
|
||||||
fi
|
[[ -z $forbidden ]] || {
|
||||||
if [ -f completions/_vaptvupt ]; then
|
printf '%s\n' "$forbidden" >&2
|
||||||
install -m 0644 completions/_vaptvupt "$ROOT/usr/share/zsh/site-functions/_$PKGNAME"
|
die 'compiled library or object found in package staging tree'
|
||||||
ln -sf _$PKGNAME "$ROOT/usr/share/zsh/site-functions/_$LEGACY"
|
}
|
||||||
fi
|
|
||||||
if [ -f completions/vaptvupt.fish ]; then
|
docdir=$stage/usr/share/doc/zupt
|
||||||
install -m 0644 completions/vaptvupt.fish "$ROOT/usr/share/fish/vendor_completions.d/$PKGNAME.fish"
|
mkdir -p -- "$docdir"
|
||||||
|
install -m 0644 README.md CHANGELOG.md SECURITY.md "$docdir/"
|
||||||
|
for document in THREAT_MODEL.md NOTICE THIRD-PARTY-NOTICES.md LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0; do
|
||||||
|
[[ ! -f $document ]] || install -m 0644 "$document" "$docdir/"
|
||||||
|
done
|
||||||
|
install -m 0644 LICENSE "$docdir/copyright"
|
||||||
|
|
||||||
|
mkdir -p -- "$work/debian" "$stage/DEBIAN"
|
||||||
|
if [[ -n ${DEB_DEPENDS:-} ]]; then
|
||||||
|
depends=$DEB_DEPENDS
|
||||||
|
else
|
||||||
|
command -v dpkg-shlibdeps >/dev/null 2>&1 || \
|
||||||
|
die 'dpkg-shlibdeps is required unless DEB_DEPENDS is explicitly set'
|
||||||
|
printf 'Source: zupt\nPackage: zupt\n' > "$work/debian/control"
|
||||||
|
shlib_line=$(cd -- "$work" && dpkg-shlibdeps -O -e"$binary")
|
||||||
|
depends=${shlib_line#shlibs:Depends=}
|
||||||
|
[[ -n $depends && $depends != "$shlib_line" ]] || \
|
||||||
|
die 'dpkg-shlibdeps did not determine runtime dependencies'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Docs
|
installed_kib=$(du -sk "$stage/usr" | awk '{print $1}')
|
||||||
install -m 0644 README.md "$ROOT/usr/share/doc/$PKGNAME/README.md"
|
cat > "$stage/DEBIAN/control" <<EOF
|
||||||
install -m 0644 LICENSE "$ROOT/usr/share/doc/$PKGNAME/copyright"
|
Package: zupt
|
||||||
[ -f SECURITY.md ] && install -m 0644 SECURITY.md "$ROOT/usr/share/doc/$PKGNAME/SECURITY.md"
|
Version: $version
|
||||||
[ -f CHANGELOG.md ] && install -m 0644 CHANGELOG.md "$ROOT/usr/share/doc/$PKGNAME/CHANGELOG.md"
|
|
||||||
[ -f THREAT_MODEL.md ] && install -m 0644 THREAT_MODEL.md "$ROOT/usr/share/doc/$PKGNAME/THREAT_MODEL.md"
|
|
||||||
|
|
||||||
# DEBIAN/control
|
|
||||||
INSTALLED_KB=$(du -sk "$ROOT/usr" | awk '{print $1}')
|
|
||||||
cat > "$ROOT/DEBIAN/control" <<EOF
|
|
||||||
Package: $PKGNAME
|
|
||||||
Version: $VERSION
|
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Architecture: $ARCH
|
Architecture: $arch
|
||||||
Provides: $LEGACY (= $VERSION)
|
Depends: $depends
|
||||||
Replaces: $LEGACY (<< 3.0.0)
|
Installed-Size: $installed_kib
|
||||||
Conflicts: $LEGACY (<< 3.0.0)
|
|
||||||
Depends: libargon2-1, libssl3 | libssl3t64
|
|
||||||
Installed-Size: $INSTALLED_KB
|
|
||||||
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
|
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
|
||||||
Homepage: https://git.securityops.co/cristiancmoises/zupt
|
Homepage: https://github.com/cristiancmoises/zupt
|
||||||
Description: Post-quantum backup compression utility (formerly zupt)
|
Description: Backup compression with authenticated and post-quantum encryption
|
||||||
VaptVupt (renamed from Zupt in v3.0.0 due to a prior INPI Brasil
|
ZUPT creates compressed backup archives with optional password encryption
|
||||||
trademark on the name) is a pure-C11 backup compression utility
|
or ML-KEM-768 and X25519 hybrid key encapsulation. This package is built from
|
||||||
featuring post-quantum hybrid encryption (ML-KEM-768 + X25519,
|
source with the optional libvuptsdk and libpqvaptvupt integrations disabled.
|
||||||
FIPS 203), AES-256-CTR + HMAC-SHA256 authenticated encryption,
|
|
||||||
Argon2id KDF (PBKDF2-SHA256 via --kdf pbkdf2), multi-threaded
|
|
||||||
compression with the VaptVupt LZ + ANS codec 2.48.5, full-disk
|
|
||||||
backup with sparse-region detection, and end-to-end byte-level
|
|
||||||
tamper detection on encrypted archives (F-09: 0/1827 silent
|
|
||||||
accepts).
|
|
||||||
.
|
|
||||||
The .zupt archive extension is unchanged; v2.x and v3.0.0
|
|
||||||
archives are bidirectionally compatible. The legacy /usr/bin/zupt
|
|
||||||
symlink is preserved for one major version cycle.
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
DEB_OUT="/tmp/${PKGNAME}_${VERSION}_${ARCH}.deb"
|
package_tmp=$work/$(basename -- "$output")
|
||||||
dpkg-deb --build --root-owner-group "$ROOT" "$DEB_OUT" >/dev/null
|
dpkg-deb --build --root-owner-group "$stage" "$package_tmp" >/dev/null
|
||||||
echo "Built: $DEB_OUT ($(du -h "$DEB_OUT" | cut -f1))"
|
dpkg-deb --info "$package_tmp" >/dev/null
|
||||||
dpkg-deb -I "$DEB_OUT" | sed -n '1,20p'
|
dpkg-deb --contents "$package_tmp" > "$work/contents.txt"
|
||||||
|
if grep -Eq '(/usr/bin/vaptvupt$|\.(o|obj|a|so|so\.[^/]+|dll|dylib)$)' "$work/contents.txt"; then
|
||||||
|
cat "$work/contents.txt" >&2
|
||||||
|
die 'forbidden alias or compiled library/object found in .deb contents'
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p -- "$extract"
|
||||||
|
dpkg-deb --extract "$package_tmp" "$extract"
|
||||||
|
bash scripts/test-installed-zupt.sh "$extract/usr/bin/zupt"
|
||||||
|
|
||||||
|
mv -- "$package_tmp" "$output"
|
||||||
|
sha256sum "$output"
|
||||||
|
printf 'PASS: built and extracted-package-tested %s\n' "$output"
|
||||||
|
|
|
||||||
|
|
@ -1,188 +1,228 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
#
|
|
||||||
# Build a macOS .dmg installer for the Zupt CLI.
|
|
||||||
#
|
|
||||||
# This script MUST be run on macOS — `hdiutil` is required and only
|
|
||||||
# ships with macOS. There is no portable way to produce a .dmg from
|
|
||||||
# Linux that Apple's installer will mount cleanly (libdmg-hfsplus and
|
|
||||||
# dmg2img exist but produce read-only images that some macOS versions
|
|
||||||
# reject).
|
|
||||||
#
|
|
||||||
# On macOS:
|
|
||||||
# xcode-select --install # one-time, for clang
|
|
||||||
# make # build the zupt binary
|
|
||||||
# VERSION=2.4.7 bash packaging/build-dmg.sh
|
|
||||||
#
|
|
||||||
# Produces: /tmp/Zupt-VERSION.dmg with:
|
|
||||||
# - zupt binary (universal2 if built with -arch x86_64 -arch arm64)
|
|
||||||
# - libzuptsdk dylib alongside the binary at @loader_path
|
|
||||||
# - install.command (drag-to-install script)
|
|
||||||
# - README.md, LICENSE
|
|
||||||
# - Optional: code-signed and notarized if APPLE_DEV_ID env is set
|
|
||||||
#
|
|
||||||
# For Homebrew installation, prefer packaging/homebrew/zupt.rb instead.
|
|
||||||
# The .dmg is for users who don't want to install Homebrew.
|
|
||||||
|
|
||||||
set -e
|
set -Eeuo pipefail
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
|
|
||||||
VERSION="${VERSION:-2.4.7}"
|
umask 022
|
||||||
ARCH="${ARCH:-$(uname -m)}" # x86_64 or arm64
|
export LC_ALL=C
|
||||||
NAME="Zupt-${VERSION}-${ARCH}"
|
|
||||||
STAGE="/tmp/${NAME}.app/Contents"
|
|
||||||
|
|
||||||
# ── Platform check ──
|
die() {
|
||||||
if [ "$(uname)" != "Darwin" ]; then
|
printf 'FAIL: %s\n' "$*" >&2
|
||||||
cat >&2 <<EOF
|
|
||||||
ERROR: build-dmg.sh must be run on macOS.
|
|
||||||
|
|
||||||
The .dmg format requires Apple's hdiutil. On Linux:
|
|
||||||
- Use the .deb (packaging/build-deb.sh) for Debian/Ubuntu/Mint
|
|
||||||
- Use the .rpm (packaging/build-rpm.sh) for Fedora/RHEL/openSUSE
|
|
||||||
- Use the AppImage (packaging/build-appimage.sh) for universal Linux
|
|
||||||
- Use the Homebrew formula on macOS (packaging/homebrew/zupt.rb)
|
|
||||||
|
|
||||||
If you need a macOS .pkg without macOS hardware, GitHub Actions has
|
|
||||||
macos-14 runners that can produce signed .dmg/.pkg artefacts. See
|
|
||||||
.github/workflows/ci.yml for the matrix template.
|
|
||||||
EOF
|
|
||||||
exit 1
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ $(uname -s) == Darwin ]] || die 'DMG packages must be built and tested on macOS'
|
||||||
|
|
||||||
|
test_macos_binary() (
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
local candidate=$1 binary test_root archive_size
|
||||||
|
if [[ $candidate == */* ]]; then
|
||||||
|
[[ -x $candidate ]] || die "executable not found: $candidate"
|
||||||
|
binary=$(cd "$(dirname "$candidate")" && pwd -P)/$(basename "$candidate")
|
||||||
|
else
|
||||||
|
binary=$(command -v "$candidate" || true)
|
||||||
|
[[ -n $binary ]] || die "executable not found on PATH: $candidate"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for command_name in cmp dd diff find grep shasum sort; do
|
||||||
|
command -v "$command_name" >/dev/null 2>&1 || \
|
||||||
|
die "required smoke-test command not found: $command_name"
|
||||||
|
done
|
||||||
|
|
||||||
|
test_root=$(mktemp -d "${TMPDIR:-/tmp}/zupt-macos-smoke.XXXXXX")
|
||||||
|
trap 'chmod -R u+rwX "$test_root" 2>/dev/null || true; rm -rf "$test_root"' \
|
||||||
|
EXIT HUP INT TERM
|
||||||
|
mkdir -p "$test_root/input/subdir" "$test_root/output" \
|
||||||
|
"$test_root/password-output" "$test_root/escape-output" "$test_root/outside"
|
||||||
|
printf 'ZUPT macOS package smoke test\n' > "$test_root/input/text file.txt"
|
||||||
|
printf 'conteúdo UTF-8\n' > "$test_root/input/subdir/café-安全.txt"
|
||||||
|
: > "$test_root/input/empty file"
|
||||||
|
dd if=/dev/urandom of="$test_root/input/subdir/random.bin" \
|
||||||
|
bs=4096 count=8 >/dev/null 2>&1
|
||||||
|
printf 'do-not-overwrite\n' > "$test_root/outside/sentinel"
|
||||||
|
|
||||||
|
"$binary" --version > "$test_root/version.log" 2>&1
|
||||||
|
grep -q '^zupt ' "$test_root/version.log"
|
||||||
|
"$binary" --help > "$test_root/help.log" 2>&1
|
||||||
|
grep -q '^Usage:' "$test_root/help.log"
|
||||||
|
if "$binary" --definitely-invalid-option >/dev/null 2>&1; then
|
||||||
|
die 'invalid option returned success'
|
||||||
|
fi
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$test_root"
|
||||||
|
"$binary" compress plain.zupt input
|
||||||
|
"$binary" test plain.zupt
|
||||||
|
"$binary" extract -o output plain.zupt
|
||||||
|
)
|
||||||
|
diff -r "$test_root/input" "$test_root/output/input"
|
||||||
|
(
|
||||||
|
cd "$test_root/input"
|
||||||
|
find . -type f -exec shasum -a 256 {} \; | sort
|
||||||
|
) > "$test_root/original.sha256"
|
||||||
|
(
|
||||||
|
cd "$test_root/output/input"
|
||||||
|
find . -type f -exec shasum -a 256 {} \; | sort
|
||||||
|
) > "$test_root/extracted.sha256"
|
||||||
|
cmp "$test_root/original.sha256" "$test_root/extracted.sha256"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$test_root"
|
||||||
|
"$binary" compress -p 'ZUPT-test-password-2026!' \
|
||||||
|
password.zupt 'input/text file.txt'
|
||||||
|
"$binary" test -p 'ZUPT-test-password-2026!' password.zupt
|
||||||
|
"$binary" extract -p 'ZUPT-test-password-2026!' \
|
||||||
|
-o password-output password.zupt
|
||||||
|
)
|
||||||
|
cmp "$test_root/input/text file.txt" \
|
||||||
|
"$test_root/password-output/input/text file.txt"
|
||||||
|
if "$binary" extract -p incorrect-password -o "$test_root/wrong-password" \
|
||||||
|
"$test_root/password.zupt" >/dev/null 2>&1; then
|
||||||
|
die 'incorrect password returned success'
|
||||||
|
fi
|
||||||
|
|
||||||
|
archive_size=$(wc -c < "$test_root/plain.zupt")
|
||||||
|
((archive_size > 32)) || die 'archive unexpectedly small'
|
||||||
|
dd if="$test_root/plain.zupt" of="$test_root/corrupt.zupt" bs=1 \
|
||||||
|
count="$((archive_size - 17))" >/dev/null 2>&1
|
||||||
|
if "$binary" test "$test_root/corrupt.zupt" >/dev/null 2>&1; then
|
||||||
|
die 'truncated archive returned success'
|
||||||
|
fi
|
||||||
|
|
||||||
|
ln -s "$test_root/outside" "$test_root/escape-output/input"
|
||||||
|
"$binary" extract -o "$test_root/escape-output" \
|
||||||
|
"$test_root/plain.zupt" >/dev/null 2>&1 || true
|
||||||
|
[[ $(<"$test_root/outside/sentinel") == do-not-overwrite ]] || \
|
||||||
|
die 'extraction overwrote outside sentinel'
|
||||||
|
[[ ! -e $test_root/outside/text\ file.txt && ! -e $test_root/outside/subdir ]] || \
|
||||||
|
die 'extraction escaped through a destination symlink'
|
||||||
|
|
||||||
|
[[ $(id -u) -ne 0 ]] || die 'macOS package smoke test unexpectedly ran as root'
|
||||||
|
printf 'PASS: native macOS package functional test suite\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ ${1:-} == --test-binary ]]; then
|
||||||
|
(($# == 2)) || die 'usage: build-dmg.sh --test-binary PATH'
|
||||||
|
test_macos_binary "$2"
|
||||||
|
exit 0
|
||||||
|
elif (($# != 0)); then
|
||||||
|
die 'usage: build-dmg.sh [--test-binary PATH]'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Build zupt (universal binary if possible) ──
|
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||||
echo "[dmg] Building zupt"
|
cd -- "$repo_root"
|
||||||
|
|
||||||
|
header_version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
|
version=${VERSION:-$header_version}
|
||||||
|
[[ -n $version && $version == "$header_version" ]] || \
|
||||||
|
die "VERSION '$version' does not match include/zupt.h '$header_version'"
|
||||||
|
[[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "invalid package version: $version"
|
||||||
|
native_arch=$(uname -m)
|
||||||
|
arch=${ARCH:-$native_arch}
|
||||||
|
[[ $arch == "$native_arch" ]] || \
|
||||||
|
die "ARCH=$arch does not match the native macOS architecture $native_arch"
|
||||||
|
|
||||||
|
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
|
||||||
|
mkdir -p "$dist_dir"
|
||||||
|
dist_dir=$(cd "$dist_dir" && pwd -P)
|
||||||
|
output=$dist_dir/ZUPT-${version}-macOS-${arch}.dmg
|
||||||
|
[[ ! -e $output ]] || die "refusing to overwrite existing output: $output"
|
||||||
|
|
||||||
|
for command_name in make clang hdiutil otool plutil shasum; do
|
||||||
|
command -v -- "$command_name" >/dev/null 2>&1 || die "required command not found: $command_name"
|
||||||
|
done
|
||||||
|
run_checks=${RUN_CHECKS:-1}
|
||||||
|
[[ $run_checks == 0 || $run_checks == 1 ]] || die 'RUN_CHECKS must be 0 or 1'
|
||||||
|
if [[ $run_checks == 1 ]]; then
|
||||||
|
command -v git >/dev/null 2>&1 || die 'git is required when RUN_CHECKS=1'
|
||||||
|
fi
|
||||||
|
|
||||||
|
jobs=${JOBS:-$(sysctl -n hw.ncpu 2>/dev/null || printf '1')}
|
||||||
|
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-dmg.XXXXXXXX")
|
||||||
|
app=$work/ZUPT.app
|
||||||
|
contents=$app/Contents
|
||||||
|
dmg_root=$work/dmg-root
|
||||||
|
dmg_tmp=$work/$(basename "$output")
|
||||||
|
mkdir -p "$contents/MacOS" "$contents/Resources" "$dmg_root"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
make -C "$repo_root" clean >/dev/null 2>&1 || true
|
||||||
|
chmod -R u+rwX "$work" 2>/dev/null || true
|
||||||
|
rm -rf "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
|
printf '[dmg] source-only build of ZUPT %s (%s)\n' "$version" "$arch"
|
||||||
make clean
|
make clean
|
||||||
if xcrun --sdk macosx clang -dM -E - </dev/null | grep -q __aarch64__; then
|
make -j"$jobs" CC=clang V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
|
||||||
# arm64 host → can cross-build for x86_64 via -arch flag
|
if [[ $run_checks == 1 ]]; then
|
||||||
CFLAGS="-O2 -std=c11 -arch arm64 -arch x86_64" \
|
make CC=clang V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
|
||||||
LDFLAGS="-arch arm64 -arch x86_64" \
|
fi
|
||||||
make -j"$(sysctl -n hw.ncpu)" || make -j"$(sysctl -n hw.ncpu)"
|
test_macos_binary "$repo_root/zupt"
|
||||||
else
|
|
||||||
make -j"$(sysctl -n hw.ncpu)"
|
install -m 0755 zupt "$contents/MacOS/zupt"
|
||||||
|
for document in README.md CHANGELOG.md LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE THIRD-PARTY-NOTICES.md; do
|
||||||
|
[[ ! -f $document ]] || install -m 0644 "$document" "$contents/Resources/"
|
||||||
|
done
|
||||||
|
|
||||||
|
if otool -l "$contents/MacOS/zupt" | grep -q 'cmd LC_RPATH'; then
|
||||||
|
otool -l "$contents/MacOS/zupt" >&2
|
||||||
|
die 'macOS executable contains LC_RPATH'
|
||||||
|
fi
|
||||||
|
if otool -L "$contents/MacOS/zupt" | grep -Eqi \
|
||||||
|
'(vendor/|libvuptsdk|libpqvaptvupt|/home/|/Users/[^/]+/|/opt/(homebrew|local)/|/usr/local/)'; then
|
||||||
|
otool -L "$contents/MacOS/zupt" >&2
|
||||||
|
die 'macOS executable references a build path or vendored optional library'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Stage the .app bundle ──
|
cat > "$contents/Info.plist" <<EOF
|
||||||
echo "[dmg] Staging .app bundle"
|
|
||||||
rm -rf "/tmp/${NAME}.app"
|
|
||||||
mkdir -p "$STAGE/MacOS" "$STAGE/Resources" "$STAGE/Frameworks"
|
|
||||||
|
|
||||||
install -m 755 zupt "$STAGE/MacOS/zupt"
|
|
||||||
|
|
||||||
# Vendored libzuptsdk — on macOS it'd be .dylib, but if the vendored
|
|
||||||
# build is Linux-style .so, ship that and warn. A proper macOS build
|
|
||||||
# would produce libzuptsdk.2.0.0.dylib.
|
|
||||||
if [ -f vendor/zuptsdk/libzuptsdk.2.0.0.dylib ]; then
|
|
||||||
install -m 755 vendor/zuptsdk/libzuptsdk.2.0.0.dylib "$STAGE/Frameworks/"
|
|
||||||
install_name_tool -id "@loader_path/../Frameworks/libzuptsdk.2.0.0.dylib" \
|
|
||||||
"$STAGE/Frameworks/libzuptsdk.2.0.0.dylib"
|
|
||||||
install_name_tool -change "vendor/zuptsdk/libzuptsdk.so.2" \
|
|
||||||
"@loader_path/../Frameworks/libzuptsdk.2.0.0.dylib" \
|
|
||||||
"$STAGE/MacOS/zupt"
|
|
||||||
elif [ -f vendor/zuptsdk/libzuptsdk.so.2.0.0 ]; then
|
|
||||||
cat >&2 <<EOF
|
|
||||||
WARNING: vendor/zuptsdk ships .so (Linux), not .dylib (macOS).
|
|
||||||
The .dmg will include the Linux library which won't load on macOS.
|
|
||||||
Build libzuptsdk natively on macOS first, or modify the Makefile
|
|
||||||
to produce .dylib output on Darwin.
|
|
||||||
EOF
|
|
||||||
install -m 755 vendor/zuptsdk/libzuptsdk.so.2.0.0 "$STAGE/Frameworks/"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Info.plist (minimal — zupt is a CLI, so the .app is mostly a wrapper)
|
|
||||||
cat > "$STAGE/Info.plist" <<PLIST
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key><string>dev.zupt.cli</string>
|
||||||
<string>co.securityops.zupt</string>
|
<key>CFBundleName</key><string>ZUPT</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleDisplayName</key><string>ZUPT</string>
|
||||||
<string>Zupt</string>
|
<key>CFBundleExecutable</key><string>zupt</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundlePackageType</key><string>APPL</string>
|
||||||
<string>Zupt</string>
|
<key>CFBundleVersion</key><string>$version</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleShortVersionString</key><string>$version</string>
|
||||||
<string>${VERSION}</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>${VERSION}</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>zupt</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>NSHighResolutionCapable</key>
|
|
||||||
<true/>
|
|
||||||
<key>LSMinimumSystemVersion</key>
|
|
||||||
<string>11.0</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
PLIST
|
EOF
|
||||||
|
plutil -lint "$contents/Info.plist"
|
||||||
|
|
||||||
cp README.md "$STAGE/Resources/" 2>/dev/null || true
|
if [[ -n ${CODESIGN_IDENTITY:-} ]]; then
|
||||||
cp LICENSE "$STAGE/Resources/" 2>/dev/null || true
|
codesign --force --options runtime --timestamp --sign "$CODESIGN_IDENTITY" "$app"
|
||||||
|
codesign --verify --deep --strict "$app"
|
||||||
# ── Drag-to-install command file ──
|
|
||||||
cat > "/tmp/${NAME}-install.command" <<'INSTALL'
|
|
||||||
#!/bin/bash
|
|
||||||
# Drag-installer for Zupt CLI. Copies the binary to /usr/local/bin
|
|
||||||
# (or the user's ~/bin if /usr/local isn't writable).
|
|
||||||
set -e
|
|
||||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
APP="$DIR/Zupt.app"
|
|
||||||
TARGET="/usr/local/bin"
|
|
||||||
if [ ! -w "$TARGET" ]; then
|
|
||||||
TARGET="$HOME/bin"
|
|
||||||
mkdir -p "$TARGET"
|
|
||||||
echo "Installing to $TARGET (add to PATH if missing)"
|
|
||||||
fi
|
|
||||||
cp "$APP/Contents/MacOS/zupt" "$TARGET/zupt"
|
|
||||||
chmod 755 "$TARGET/zupt"
|
|
||||||
# Bundle the dylib alongside under a stable path
|
|
||||||
LIBDIR="/usr/local/lib/zupt"
|
|
||||||
[ -w /usr/local/lib ] || LIBDIR="$HOME/lib/zupt"
|
|
||||||
mkdir -p "$LIBDIR"
|
|
||||||
if [ -d "$APP/Contents/Frameworks" ]; then
|
|
||||||
cp -P "$APP/Contents/Frameworks"/* "$LIBDIR/" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
echo "Installed: $TARGET/zupt"
|
|
||||||
"$TARGET/zupt" version
|
|
||||||
INSTALL
|
|
||||||
chmod 755 "/tmp/${NAME}-install.command"
|
|
||||||
|
|
||||||
# ── Optional: code sign ──
|
|
||||||
if [ -n "${APPLE_DEV_ID:-}" ]; then
|
|
||||||
echo "[dmg] Code-signing with Developer ID: $APPLE_DEV_ID"
|
|
||||||
codesign --force --options runtime --sign "$APPLE_DEV_ID" \
|
|
||||||
--entitlements packaging/macos/entitlements.plist \
|
|
||||||
"$STAGE/MacOS/zupt" 2>&1 || echo " (no entitlements file — proceeding unsigned for hardening)"
|
|
||||||
codesign --force --sign "$APPLE_DEV_ID" "/tmp/${NAME}.app" || true
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Build .dmg ──
|
cp -R "$app" "$dmg_root/ZUPT.app"
|
||||||
echo "[dmg] Building disk image"
|
cat > "$dmg_root/Install ZUPT.command" <<'EOF'
|
||||||
DMG="/tmp/${NAME}.dmg"
|
#!/usr/bin/env bash
|
||||||
rm -f "$DMG"
|
set -Eeuo pipefail
|
||||||
|
installer_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
|
||||||
# Stage a directory tree that becomes the .dmg root
|
source_binary=$installer_dir/ZUPT.app/Contents/MacOS/zupt
|
||||||
DMGSRC="/tmp/${NAME}-dmgsrc"
|
target_dir=/usr/local/bin
|
||||||
rm -rf "$DMGSRC"
|
if [[ ! -d $target_dir || ! -w $target_dir ]]; then
|
||||||
mkdir -p "$DMGSRC"
|
target_dir=${XDG_BIN_HOME:-$HOME/.local/bin}
|
||||||
cp -R "/tmp/${NAME}.app" "$DMGSRC/Zupt.app"
|
mkdir -p "$target_dir"
|
||||||
cp "/tmp/${NAME}-install.command" "$DMGSRC/Install Zupt.command"
|
|
||||||
[ -f README.md ] && cp README.md "$DMGSRC/"
|
|
||||||
[ -f LICENSE ] && cp LICENSE "$DMGSRC/"
|
|
||||||
|
|
||||||
hdiutil create -fs HFS+ -srcfolder "$DMGSRC" -volname "Zupt ${VERSION}" \
|
|
||||||
-format UDZO -ov "$DMG"
|
|
||||||
|
|
||||||
# ── Optional: notarize ──
|
|
||||||
if [ -n "${APPLE_DEV_ID:-}" ] && [ -n "${APPLE_NOTARIZE_KEY:-}" ]; then
|
|
||||||
echo "[dmg] Submitting for notarization"
|
|
||||||
xcrun notarytool submit "$DMG" --apple-id "$APPLE_DEV_ID" \
|
|
||||||
--password "$APPLE_NOTARIZE_KEY" --wait
|
|
||||||
xcrun stapler staple "$DMG"
|
|
||||||
fi
|
fi
|
||||||
|
install -m 0755 "$source_binary" "$target_dir/zupt"
|
||||||
|
printf 'Installed %s\n' "$target_dir/zupt"
|
||||||
|
"$target_dir/zupt" --version
|
||||||
|
EOF
|
||||||
|
chmod 0755 "$dmg_root/Install ZUPT.command"
|
||||||
|
for document in README.md CHANGELOG.md LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE THIRD-PARTY-NOTICES.md; do
|
||||||
|
[[ ! -f $document ]] || install -m 0644 "$document" "$dmg_root/"
|
||||||
|
done
|
||||||
|
|
||||||
echo ""
|
hdiutil create -fs HFS+ -srcfolder "$dmg_root" -volname "ZUPT $version" \
|
||||||
echo "Built: $DMG ($(du -h "$DMG" | cut -f1))"
|
-format UDZO -ov "$dmg_tmp"
|
||||||
echo "Users mount and drag 'Zupt.app' or double-click 'Install Zupt.command'."
|
hdiutil verify "$dmg_tmp"
|
||||||
|
|
||||||
|
mv "$dmg_tmp" "$output"
|
||||||
|
shasum -a 256 "$output"
|
||||||
|
printf 'PASS: built and native-binary-tested %s\n' "$output"
|
||||||
|
|
|
||||||
|
|
@ -1,121 +1,140 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
# Build zupt-gui AppImage. Since the GUI is pure Python + Qt, the AppDir
|
|
||||||
# bundles only the Python source and metadata; it relies on system
|
|
||||||
# python3 + PyQt6/PySide6 at runtime. This keeps the AppImage tiny
|
|
||||||
# (~50 KB) and lets it work on any Linux with Qt6 Python bindings.
|
|
||||||
#
|
|
||||||
# For a true self-contained AppImage with bundled Python interpreter,
|
|
||||||
# use python-appimage (https://github.com/niess/python-appimage) on
|
|
||||||
# the build host — it produces a ~80 MB AppImage. The portable variant
|
|
||||||
# below is the better tradeoff for most distributions.
|
|
||||||
set -e
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
|
|
||||||
VERSION="${VERSION:-1.2.0}"
|
# Build a dependency-light GUI AppImage. The ZUPT CLI is compiled from this
|
||||||
APPDIR="/tmp/vaptvupt-gui.AppDir"
|
# tree and bundled; Python 3 plus PySide6 or PyQt6 remain host requirements.
|
||||||
|
|
||||||
rm -rf "$APPDIR"
|
set -Eeuo pipefail
|
||||||
mkdir -p "$APPDIR/usr/bin" \
|
umask 022
|
||||||
"$APPDIR/usr/lib/vaptvupt-gui" \
|
export LC_ALL=C
|
||||||
"$APPDIR/usr/share/applications" \
|
|
||||||
"$APPDIR/usr/share/icons/hicolor/256x256/apps"
|
|
||||||
|
|
||||||
# Python source
|
die() { printf 'FAIL: %s\n' "$*" >&2; exit 1; }
|
||||||
install -m 644 gui/src/zupt_gui.py "$APPDIR/usr/lib/vaptvupt-gui/"
|
|
||||||
|
|
||||||
# Wrapper
|
[[ $(uname -s) == Linux ]] || die 'AppImage packages must be built on Linux'
|
||||||
cat > "$APPDIR/usr/bin/vaptvupt-gui" <<'WRAP'
|
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||||
|
cd -- "$repo_root"
|
||||||
|
header_version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
|
version=${VERSION:-$header_version}
|
||||||
|
[[ $version == "$header_version" && $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || \
|
||||||
|
die "VERSION '$version' does not match include/zupt.h '$header_version'"
|
||||||
|
|
||||||
|
case $(uname -m) in
|
||||||
|
x86_64|amd64) native_arch=x86_64 ;;
|
||||||
|
aarch64|arm64) native_arch=aarch64 ;;
|
||||||
|
*) die "unsupported native AppImage architecture: $(uname -m)" ;;
|
||||||
|
esac
|
||||||
|
case ${ARCH:-$native_arch} in
|
||||||
|
x86_64|amd64) arch=x86_64 ;;
|
||||||
|
aarch64|arm64) arch=aarch64 ;;
|
||||||
|
*) die "unsupported AppImage architecture: ${ARCH:-$native_arch}" ;;
|
||||||
|
esac
|
||||||
|
[[ $arch == "$native_arch" ]] || \
|
||||||
|
die "ARCH=$arch does not match the native build architecture $native_arch"
|
||||||
|
|
||||||
|
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
|
||||||
|
mkdir -p -- "$dist_dir"
|
||||||
|
dist_dir=$(cd -- "$dist_dir" && pwd -P)
|
||||||
|
case $dist_dir/ in "$repo_root"/*) die 'DIST_DIR must be outside the repository' ;; esac
|
||||||
|
output=$dist_dir/ZUPT-GUI-$version-linux-$arch.AppImage
|
||||||
|
[[ ! -e $output ]] || die "refusing to overwrite existing output: $output"
|
||||||
|
|
||||||
|
appimagetool=${APPIMAGETOOL:-appimagetool}
|
||||||
|
appimagetool=$(command -v -- "$appimagetool" 2>/dev/null || true)
|
||||||
|
[[ -n $appimagetool ]] || die 'appimagetool not found; no network fallback is performed'
|
||||||
|
runtime_file=${APPIMAGE_RUNTIME_FILE:-}
|
||||||
|
[[ -n $runtime_file && -s $runtime_file ]] || \
|
||||||
|
die 'set APPIMAGE_RUNTIME_FILE to a non-empty verified local type-2 runtime'
|
||||||
|
runtime_file=$(cd -- "$(dirname -- "$runtime_file")" && pwd -P)/$(basename -- "$runtime_file")
|
||||||
|
runtime_compliance_file=${APPIMAGE_RUNTIME_COMPLIANCE_FILE:-}
|
||||||
|
[[ -n $runtime_compliance_file && -s $runtime_compliance_file ]] || \
|
||||||
|
die 'set APPIMAGE_RUNTIME_COMPLIANCE_FILE to the audited runtime license/source-compliance notice'
|
||||||
|
runtime_compliance_file=$(cd -- "$(dirname -- "$runtime_compliance_file")" && pwd -P)/$(basename -- "$runtime_compliance_file")
|
||||||
|
|
||||||
|
for command_name in make python3 readelf file sha256sum; do
|
||||||
|
command -v -- "$command_name" >/dev/null 2>&1 || \
|
||||||
|
die "required command not found: $command_name"
|
||||||
|
done
|
||||||
|
python3 -c 'import PySide6.QtWidgets' 2>/dev/null || \
|
||||||
|
python3 -c 'import PyQt6.QtWidgets' 2>/dev/null || \
|
||||||
|
die 'the build/test host needs PySide6 or PyQt6; the AppImage does not download it'
|
||||||
|
|
||||||
|
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')}
|
||||||
|
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-gui-appimage.XXXXXXXX")
|
||||||
|
appdir=$work/ZUPT-GUI.AppDir
|
||||||
|
image_tmp=$work/$(basename -- "$output")
|
||||||
|
cleanup() {
|
||||||
|
make -C "$repo_root" clean >/dev/null 2>&1 || true
|
||||||
|
chmod -R u+rwX "$work" 2>/dev/null || true
|
||||||
|
rm -rf -- "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
|
make clean
|
||||||
|
make -j"$jobs" V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
|
||||||
|
make V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
|
||||||
|
make DESTDIR="$appdir" PREFIX=/usr WITH_SDK=0 WITH_PQBOX=0 \
|
||||||
|
INSTALL_LEGACY_ALIAS=0 install
|
||||||
|
|
||||||
|
binary=$appdir/usr/bin/zupt
|
||||||
|
[[ -x $binary ]] || die 'source-built CLI is missing from AppDir'
|
||||||
|
[[ ! -e $appdir/usr/bin/vaptvupt ]] || die 'legacy vaptvupt alias must not be packaged'
|
||||||
|
if readelf -d "$binary" 2>/dev/null | grep -Eq '(RPATH|RUNPATH|libvuptsdk|libpqvaptvupt|vendor/)'; then
|
||||||
|
readelf -d "$binary" >&2
|
||||||
|
die 'CLI has RPATH/RUNPATH or an optional-library reference'
|
||||||
|
fi
|
||||||
|
|
||||||
|
install -Dm0644 gui/src/zupt_gui.py "$appdir/usr/lib/zupt-gui/zupt_gui.py"
|
||||||
|
install -Dm0644 gui/assets/zupt-icon.png \
|
||||||
|
"$appdir/usr/share/icons/hicolor/256x256/apps/zupt-gui.png"
|
||||||
|
install -Dm0644 gui/packaging/zupt-gui.desktop \
|
||||||
|
"$appdir/usr/share/applications/zupt-gui.desktop"
|
||||||
|
install -d "$appdir/usr/share/licenses/zupt"
|
||||||
|
install -m 0644 LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 \
|
||||||
|
LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE \
|
||||||
|
THIRD-PARTY-NOTICES.md "$appdir/usr/share/licenses/zupt/"
|
||||||
|
install -d "$appdir/usr/share/licenses/zupt-gui"
|
||||||
|
install -m 0644 LICENSE-AGPL-3.0 \
|
||||||
|
"$appdir/usr/share/licenses/zupt-gui/LICENSE-AGPL-3.0"
|
||||||
|
install -m 0644 gui/LICENSE-GUI \
|
||||||
|
"$appdir/usr/share/licenses/zupt-gui/LICENSE-GUI"
|
||||||
|
install -m 0644 gui/assets/README.md \
|
||||||
|
"$appdir/usr/share/licenses/zupt-gui/ASSET-PROVENANCE.md"
|
||||||
|
install -Dm0644 "$runtime_compliance_file" \
|
||||||
|
"$appdir/usr/share/licenses/zupt/AppImage-runtime-compliance.txt"
|
||||||
|
cp -- "$appdir/usr/share/icons/hicolor/256x256/apps/zupt-gui.png" \
|
||||||
|
"$appdir/zupt-gui.png"
|
||||||
|
|
||||||
|
cat >"$appdir/usr/bin/zupt-gui" <<'WRAP'
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
exec python3 "$(dirname "$0")/../lib/vaptvupt-gui/zupt_gui.py" "$@"
|
here=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P)
|
||||||
|
export ZUPT_BIN=$here/bin/zupt
|
||||||
|
exec python3 "$here/lib/zupt-gui/zupt_gui.py" "$@"
|
||||||
WRAP
|
WRAP
|
||||||
chmod 755 "$APPDIR/usr/bin/vaptvupt-gui"
|
chmod 0755 "$appdir/usr/bin/zupt-gui"
|
||||||
|
cat >"$appdir/AppRun" <<'APPRUN'
|
||||||
# Desktop file
|
|
||||||
cat > "$APPDIR/vaptvupt-gui.desktop" <<'DESKTOP'
|
|
||||||
[Desktop Entry]
|
|
||||||
Type=Application
|
|
||||||
Name=VaptVupt GUI
|
|
||||||
GenericName=Backup and Compression Utility
|
|
||||||
Comment=Post-quantum backup with HKDF combiner, key commitment, HPKE binding
|
|
||||||
Exec=vaptvupt-gui %f
|
|
||||||
Icon=vaptvupt-gui
|
|
||||||
Terminal=false
|
|
||||||
Categories=Utility;Archiving;Compression;Security;
|
|
||||||
StartupNotify=true
|
|
||||||
DESKTOP
|
|
||||||
cp "$APPDIR/vaptvupt-gui.desktop" "$APPDIR/usr/share/applications/"
|
|
||||||
|
|
||||||
# Icon
|
|
||||||
if [ -f gui/assets/zupt-icon.png ]; then
|
|
||||||
cp gui/assets/zupt-icon.png "$APPDIR/vaptvupt-gui.png"
|
|
||||||
cp gui/assets/zupt-icon.png "$APPDIR/usr/share/icons/hicolor/256x256/apps/vaptvupt-gui.png"
|
|
||||||
else
|
|
||||||
python3 -c "
|
|
||||||
import struct, zlib
|
|
||||||
def png(w, h, color):
|
|
||||||
raw = b''.join(b'\\0' + bytes(color) * w for _ in range(h))
|
|
||||||
def chunk(t, d): return struct.pack('>I', len(d)) + t + d + struct.pack('>I', zlib.crc32(t+d) & 0xffffffff)
|
|
||||||
return b'\\x89PNG\\r\\n\\x1a\\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0)) + chunk(b'IDAT', zlib.compress(raw)) + chunk(b'IEND', b'')
|
|
||||||
open('$APPDIR/zupt-gui.png','wb').write(png(256, 256, (88, 92, 215)))
|
|
||||||
"
|
|
||||||
cp "$APPDIR/vaptvupt-gui.png" "$APPDIR/usr/share/icons/hicolor/256x256/apps/vaptvupt-gui.png"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# AppRun — sets PATH so zupt-gui finds the bundled wrapper, falls
|
|
||||||
# back to system zupt CLI if not present in /usr/bin alongside.
|
|
||||||
cat > "$APPDIR/AppRun" <<'APPRUN'
|
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
HERE="$(dirname "$(readlink -f "$0")")"
|
appdir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
|
||||||
export PATH="$HERE/usr/bin:$PATH"
|
exec "$appdir/usr/bin/zupt-gui" "$@"
|
||||||
|
|
||||||
# Pre-flight check: is python3 available? Is a Qt6 binding installed?
|
|
||||||
if ! command -v python3 >/dev/null 2>&1; then
|
|
||||||
cat >&2 <<EOF
|
|
||||||
vaptvupt-gui: python3 is not installed.
|
|
||||||
Install: sudo apt install python3 (Debian/Ubuntu)
|
|
||||||
sudo dnf install python3 (Fedora/RHEL)
|
|
||||||
EOF
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! python3 -c 'import PyQt6.QtWidgets' 2>/dev/null \
|
|
||||||
&& ! python3 -c 'import PySide6.QtWidgets' 2>/dev/null; then
|
|
||||||
cat >&2 <<EOF
|
|
||||||
vaptvupt-gui: needs a Qt6 Python binding (PyQt6 or PySide6).
|
|
||||||
Install one of:
|
|
||||||
Debian/Ubuntu: sudo apt install python3-pyqt6
|
|
||||||
Fedora/RHEL: sudo dnf install python3-pyqt6
|
|
||||||
pip (any): pip install --user PySide6
|
|
||||||
EOF
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v vaptvupt >/dev/null 2>&1 && ! command -v zupt >/dev/null 2>&1; then
|
|
||||||
cat >&2 <<EOF
|
|
||||||
vaptvupt-gui: warning — neither 'vaptvupt' nor legacy 'zupt' CLI was found in PATH.
|
|
||||||
Install the vaptvupt package or place the binary in PATH.
|
|
||||||
The GUI will start but compress/extract operations will fail.
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$HERE/usr/bin/vaptvupt-gui" "$@"
|
|
||||||
APPRUN
|
APPRUN
|
||||||
chmod 755 "$APPDIR/AppRun"
|
chmod 0755 "$appdir/AppRun"
|
||||||
|
cp -- "$appdir/usr/share/applications/zupt-gui.desktop" "$appdir/"
|
||||||
|
|
||||||
# Build AppImage
|
forbidden=$(find "$appdir" -type f \( \
|
||||||
if command -v appimagetool >/dev/null 2>&1; then
|
-name '*.o' -o -name '*.obj' -o -name '*.a' -o -name '*.so' -o \
|
||||||
ARCH=x86_64 appimagetool "$APPDIR" "/tmp/VaptVupt-GUI-$VERSION-x86_64.AppImage" 2>&1 | tail -5
|
-name '*.so.*' -o -name '*.dll' -o -name '*.dylib' \
|
||||||
echo "Built: /tmp/VaptVupt-GUI-$VERSION-x86_64.AppImage"
|
\) -print)
|
||||||
else
|
[[ -z $forbidden ]] || { printf '%s\n' "$forbidden" >&2; die 'compiled library/object in AppDir'; }
|
||||||
cd /tmp
|
|
||||||
rm -f "VaptVupt-GUI-$VERSION-x86_64.AppDir.tar.gz"
|
QT_QPA_PLATFORM=offscreen "$appdir/AppRun" --version | grep -Fq "zupt-gui $version" || \
|
||||||
tar -czf "VaptVupt-GUI-$VERSION-x86_64.AppDir.tar.gz" vaptvupt-gui.AppDir
|
die 'AppDir GUI/CLI integration check failed'
|
||||||
cd - >/dev/null
|
export ARCH=$arch APPIMAGE_EXTRACT_AND_RUN=1
|
||||||
echo "appimagetool unavailable; portable AppDir tarball at:"
|
"$appimagetool" --runtime-file "$runtime_file" "$appdir" "$image_tmp"
|
||||||
echo " /tmp/VaptVupt-GUI-$VERSION-x86_64.AppDir.tar.gz"
|
chmod 0755 "$image_tmp"
|
||||||
echo "Run via: tar -xzf ... && ./vaptvupt-gui.AppDir/AppRun"
|
file "$image_tmp" | grep -q ELF || die 'generated AppImage does not have ELF magic'
|
||||||
echo "Convert to AppImage on a host with appimagetool:"
|
QT_QPA_PLATFORM=offscreen "$image_tmp" --version | grep -Fq "zupt-gui $version" || \
|
||||||
echo " ARCH=x86_64 appimagetool vaptvupt-gui.AppDir VaptVupt-GUI-$VERSION-x86_64.AppImage"
|
die 'generated AppImage execution check failed'
|
||||||
fi
|
|
||||||
|
mv -- "$image_tmp" "$output"
|
||||||
|
sha256sum "$output"
|
||||||
|
printf 'PASS: built and execution-tested %s\n' "$output"
|
||||||
|
|
|
||||||
|
|
@ -1,181 +1,113 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
# Build zupt-gui .deb (Python/Qt GUI). Works with PyQt6 OR PySide6.
|
|
||||||
set -e
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
|
|
||||||
VERSION="${VERSION:-1.2.0}"
|
# Build the architecture-independent GUI package from tracked source. The CLI
|
||||||
ARCH="all"
|
# dependency is built and tested in baseline mode but is packaged separately.
|
||||||
PKG="vaptvupt-gui_${VERSION}_${ARCH}"
|
|
||||||
ROOT="/tmp/$PKG"
|
|
||||||
|
|
||||||
rm -rf "$ROOT"
|
set -Eeuo pipefail
|
||||||
mkdir -p "$ROOT/DEBIAN" \
|
umask 022
|
||||||
"$ROOT/usr/bin" \
|
export LC_ALL=C
|
||||||
"$ROOT/usr/lib/vaptvupt-gui" \
|
|
||||||
"$ROOT/usr/share/applications" \
|
|
||||||
"$ROOT/usr/share/icons/hicolor/256x256/apps" \
|
|
||||||
"$ROOT/usr/share/man/man1" \
|
|
||||||
"$ROOT/usr/share/doc/vaptvupt-gui"
|
|
||||||
|
|
||||||
# Source files
|
die() { printf 'FAIL: %s\n' "$*" >&2; exit 1; }
|
||||||
install -m 644 gui/src/zupt_gui.py "$ROOT/usr/lib/vaptvupt-gui/"
|
|
||||||
|
|
||||||
# Wrapper script in /usr/bin
|
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||||
cat > "$ROOT/usr/bin/vaptvupt-gui" <<'WRAP'
|
cd -- "$repo_root"
|
||||||
#!/bin/sh
|
|
||||||
exec python3 /usr/lib/vaptvupt-gui/zupt_gui.py "$@"
|
|
||||||
WRAP
|
|
||||||
chmod 755 "$ROOT/usr/bin/vaptvupt-gui"
|
|
||||||
# v3.0.0: legacy zupt-gui symlink
|
|
||||||
ln -sf vaptvupt-gui "$ROOT/usr/bin/zupt-gui"
|
|
||||||
|
|
||||||
# Desktop entry
|
header_version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
cat > "$ROOT/usr/share/applications/vaptvupt-gui.desktop" <<'DESKTOP'
|
version=${VERSION:-$header_version}
|
||||||
[Desktop Entry]
|
[[ $version == "$header_version" && $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || \
|
||||||
Type=Application
|
die "VERSION '$version' does not match include/zupt.h '$header_version'"
|
||||||
Name=VaptVupt GUI
|
|
||||||
GenericName=Backup and Compression Utility
|
|
||||||
Comment=Post-quantum backup with HKDF combiner, key commitment, HPKE binding
|
|
||||||
Exec=vaptvupt-gui %f
|
|
||||||
Icon=vaptvupt-gui
|
|
||||||
Terminal=false
|
|
||||||
Categories=Utility;Archiving;Compression;Security;
|
|
||||||
StartupNotify=true
|
|
||||||
MimeType=application/x-zupt;
|
|
||||||
Keywords=archive;compression;encryption;post-quantum;backup;
|
|
||||||
DESKTOP
|
|
||||||
|
|
||||||
# Man page
|
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
|
||||||
if [ -f doc/vaptvupt-gui.1 ]; then
|
mkdir -p -- "$dist_dir"
|
||||||
install -m 644 doc/vaptvupt-gui.1 "$ROOT/usr/share/man/man1/vaptvupt-gui.1"
|
dist_dir=$(cd -- "$dist_dir" && pwd -P)
|
||||||
gzip -9n "$ROOT/usr/share/man/man1/vaptvupt-gui.1"
|
case $dist_dir/ in "$repo_root"/*) die 'DIST_DIR must be outside the repository' ;; esac
|
||||||
fi
|
output=$dist_dir/zupt-gui_${version}_all.deb
|
||||||
|
[[ ! -e $output ]] || die "refusing to overwrite existing output: $output"
|
||||||
|
|
||||||
# Icon
|
for command_name in make python3 dpkg-deb gzip sha256sum; do
|
||||||
if [ -f gui/assets/zupt-icon.png ]; then
|
command -v -- "$command_name" >/dev/null 2>&1 || \
|
||||||
cp gui/assets/zupt-icon.png "$ROOT/usr/share/icons/hicolor/256x256/apps/vaptvupt-gui.png"
|
die "required command not found: $command_name"
|
||||||
else
|
done
|
||||||
python3 -c "
|
|
||||||
import struct, zlib
|
|
||||||
def png(w, h, color):
|
|
||||||
raw = b''.join(b'\\0' + bytes(color) * w for _ in range(h))
|
|
||||||
def chunk(t, d): return struct.pack('>I', len(d)) + t + d + struct.pack('>I', zlib.crc32(t+d) & 0xffffffff)
|
|
||||||
return b'\\x89PNG\\r\\n\\x1a\\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0)) + chunk(b'IDAT', zlib.compress(raw)) + chunk(b'IEND', b'')
|
|
||||||
open('$ROOT/usr/share/icons/hicolor/256x256/apps/zupt-gui.png','wb').write(png(256, 256, (88, 92, 215)))
|
|
||||||
"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Docs
|
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')}
|
||||||
install -m 644 gui/README.md "$ROOT/usr/share/doc/vaptvupt-gui/" 2>/dev/null || true
|
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-gui-deb.XXXXXXXX")
|
||||||
gzip -9n -c CHANGELOG.md > "$ROOT/usr/share/doc/vaptvupt-gui/changelog.gz"
|
stage=$work/stage
|
||||||
|
extract=$work/extract
|
||||||
|
cleanup() {
|
||||||
|
make -C "$repo_root" clean >/dev/null 2>&1 || true
|
||||||
|
chmod -R u+rwX "$work" 2>/dev/null || true
|
||||||
|
rm -rf -- "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
cat > "$ROOT/usr/share/doc/vaptvupt-gui/copyright" <<'COPYRIGHT'
|
printf '[GUI deb] validating source-only CLI dependency %s\n' "$version"
|
||||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
make clean
|
||||||
Upstream-Name: vaptvupt-gui
|
make -j"$jobs" V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
|
||||||
Upstream-Contact: Cristian Cezar Moisés <zupt@riseup.net>
|
make V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
|
||||||
Source: https://git.securityops.co/cristiancmoises/zupt
|
./zupt version | grep -Fq "zupt $version" || die 'CLI version check failed'
|
||||||
|
|
||||||
Files: *
|
PYTHONDONTWRITEBYTECODE=1 python3 - <<'PY'
|
||||||
Copyright: 2025-2026 Cristian Cezar Moisés
|
from pathlib import Path
|
||||||
License: AGPL-3.0+
|
source = Path("gui/src/zupt_gui.py").read_text(encoding="utf-8")
|
||||||
This program is free software: you can redistribute it and/or modify
|
compile(source, "gui/src/zupt_gui.py", "exec")
|
||||||
it under the terms of the GNU Affero General Public License as
|
PY
|
||||||
published by the Free Software Foundation, either version 3 of the
|
|
||||||
License, or (at your option) any later version.
|
|
||||||
.
|
|
||||||
On Debian systems, the complete text of the GNU Affero General Public
|
|
||||||
License version 3 can be found in /usr/share/common-licenses/AGPL-3.
|
|
||||||
COPYRIGHT
|
|
||||||
|
|
||||||
# Control
|
mkdir -p -- "$stage"
|
||||||
INSTALLED_SIZE=$(du -sk "$ROOT" | cut -f1)
|
bash gui/install.sh --destdir "$stage" --prefix /usr
|
||||||
cat > "$ROOT/DEBIAN/control" <<EOF
|
[[ ! -e $stage/usr/bin/vaptvupt-gui ]] || die 'legacy vaptvupt-gui alias must not be packaged'
|
||||||
Package: vaptvupt-gui
|
|
||||||
Version: $VERSION
|
install -d -- "$stage/usr/share/doc/zupt-gui" "$stage/DEBIAN"
|
||||||
|
install -m 0644 -- gui/README.md "$stage/usr/share/doc/zupt-gui/README.md"
|
||||||
|
gzip -9n -c CHANGELOG.md >"$stage/usr/share/doc/zupt-gui/changelog.gz"
|
||||||
|
install -m 0644 -- LICENSE-AGPL-3.0 "$stage/usr/share/doc/zupt-gui/copyright"
|
||||||
|
gzip -9n -- "$stage/usr/share/man/man1/zupt-gui.1"
|
||||||
|
|
||||||
|
installed_kib=$(du -sk "$stage/usr" | awk '{print $1}')
|
||||||
|
cat >"$stage/DEBIAN/control" <<EOF
|
||||||
|
Package: zupt-gui
|
||||||
|
Version: $version
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Architecture: $ARCH
|
Architecture: all
|
||||||
Depends: python3 (>= 3.9), python3-pyqt6 | python3-pyside6, vaptvupt (>= 3.0.0) | zupt (>= 2.2.3)
|
Depends: python3 (>= 3.9), python3-pyqt6 | python3-pyside6.qtwidgets, zupt (= $version)
|
||||||
Provides: zupt-gui (= ${VERSION})
|
Installed-Size: $installed_kib
|
||||||
Replaces: zupt-gui (<< 1.2.0)
|
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
|
||||||
Conflicts: zupt-gui (<< 1.2.0)
|
Homepage: https://github.com/cristiancmoises/zupt
|
||||||
Maintainer: Cristian Cezar Moisés <zupt@riseup.net>
|
Description: Qt graphical interface for the ZUPT backup utility
|
||||||
Installed-Size: $INSTALLED_SIZE
|
The GUI creates, inspects, verifies, and extracts .zupt archives through the
|
||||||
Homepage: https://git.securityops.co/cristiancmoises/zupt
|
separately packaged zupt command. Optional SDK and PQ-box controls are
|
||||||
Description: Graphical interface for VaptVupt post-quantum backup utility
|
shown only when the installed command reports those integrations enabled.
|
||||||
PySide6/PyQt6 frontend for VaptVupt (formerly zupt-gui in 1.x). Supports compression, extraction, key
|
|
||||||
management, and full disk backup/restore. Exposes both legacy --pq
|
|
||||||
and new --pq-sdk (libzuptsdk: HKDF combiner, key commitment, HPKE
|
|
||||||
binding, Argon2id) encryption modes.
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Postinst: refresh icon cache + desktop database, print first-run guidance
|
forbidden=$(find "$stage" -type f \( \
|
||||||
cat > "$ROOT/DEBIAN/postinst" <<'POSTINST'
|
-name '*.o' -o -name '*.obj' -o -name '*.a' -o -name '*.so' -o \
|
||||||
#!/bin/sh
|
-name '*.so.*' -o -name '*.dll' -o -name '*.dylib' -o -name '*.exe' \
|
||||||
set -e
|
\) -print)
|
||||||
if [ -x /usr/bin/update-desktop-database ]; then
|
[[ -z $forbidden ]] || { printf '%s\n' "$forbidden" >&2; die 'compiled artifact in GUI package'; }
|
||||||
update-desktop-database -q /usr/share/applications || true
|
|
||||||
fi
|
package_tmp=$work/$(basename -- "$output")
|
||||||
if [ -x /usr/bin/gtk-update-icon-cache ]; then
|
source_epoch=${SOURCE_DATE_EPOCH:-$(sed -n '1p' .source-date-epoch 2>/dev/null || true)}
|
||||||
gtk-update-icon-cache -q /usr/share/icons/hicolor || true
|
[[ $source_epoch =~ ^[0-9]+$ ]] || die 'SOURCE_DATE_EPOCH is not available'
|
||||||
|
SOURCE_DATE_EPOCH=$source_epoch dpkg-deb -Zxz --build --root-owner-group \
|
||||||
|
"$stage" "$package_tmp" >/dev/null
|
||||||
|
dpkg-deb --info "$package_tmp" >/dev/null
|
||||||
|
dpkg-deb --contents "$package_tmp" >"$work/contents.txt"
|
||||||
|
grep -q './usr/bin/zupt-gui' "$work/contents.txt" || die 'GUI launcher missing from .deb'
|
||||||
|
if grep -Eq '(/usr/bin/vaptvupt-gui|\.(o|obj|a|so|so\.[^/]+|dll|dylib|exe)$)' "$work/contents.txt"; then
|
||||||
|
cat "$work/contents.txt" >&2
|
||||||
|
die 'forbidden compatibility alias or compiled artifact in .deb'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Friendly first-run check: warn the user if no Qt6 binding is installed.
|
mkdir -p -- "$extract"
|
||||||
# We don't fail the install (deb deps already enforce this); we just print
|
dpkg-deb --extract "$package_tmp" "$extract"
|
||||||
# clear guidance for users who saw "unmet dependencies" earlier.
|
PYTHONDONTWRITEBYTECODE=1 python3 - <<PY
|
||||||
if ! python3 -c 'import PyQt6.QtWidgets' 2>/dev/null \
|
from pathlib import Path
|
||||||
&& ! python3 -c 'import PySide6.QtWidgets' 2>/dev/null; then
|
p = Path("$extract/usr/lib/zupt-gui/zupt_gui.py")
|
||||||
cat << 'MSG'
|
compile(p.read_text(encoding="utf-8"), str(p), "exec")
|
||||||
|
PY
|
||||||
|
|
||||||
──────────────────────────────────────────────────────────────────────
|
mv -- "$package_tmp" "$output"
|
||||||
vaptvupt-gui installed, but no Qt6 Python binding is available.
|
sha256sum "$output"
|
||||||
|
printf 'PASS: built and content-validated %s\n' "$output"
|
||||||
Install one of the following to enable the GUI:
|
|
||||||
|
|
||||||
Debian/Ubuntu/Mint: sudo apt install python3-pyqt6
|
|
||||||
Fedora/RHEL/Rocky: sudo dnf install python3-pyqt6
|
|
||||||
Arch/Manjaro: sudo pacman -S python-pyqt6
|
|
||||||
pip (any distro): pip install --user PySide6
|
|
||||||
|
|
||||||
After installing the binding, launch with: vaptvupt-gui
|
|
||||||
──────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
MSG
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Same friendly warning if zupt CLI not installed.
|
|
||||||
if ! command -v vaptvupt >/dev/null 2>&1 && ! command -v zupt >/dev/null 2>&1; then
|
|
||||||
cat << 'MSG'
|
|
||||||
|
|
||||||
──────────────────────────────────────────────────────────────────────
|
|
||||||
vaptvupt-gui needs the 'vaptvupt' CLI to function. Install it:
|
|
||||||
|
|
||||||
Debian/Ubuntu/Mint: sudo dpkg -i vaptvupt_3.0.0_amd64.deb
|
|
||||||
(followed by: sudo apt --fix-broken install)
|
|
||||||
──────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
MSG
|
|
||||||
fi
|
|
||||||
exit 0
|
|
||||||
POSTINST
|
|
||||||
chmod 755 "$ROOT/DEBIAN/postinst"
|
|
||||||
|
|
||||||
cat > "$ROOT/DEBIAN/postrm" <<'POSTRM'
|
|
||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then
|
|
||||||
if [ -x /usr/bin/update-desktop-database ]; then
|
|
||||||
update-desktop-database -q /usr/share/applications || true
|
|
||||||
fi
|
|
||||||
if [ -x /usr/bin/gtk-update-icon-cache ]; then
|
|
||||||
gtk-update-icon-cache -q /usr/share/icons/hicolor || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
POSTRM
|
|
||||||
chmod 755 "$ROOT/DEBIAN/postrm"
|
|
||||||
|
|
||||||
dpkg-deb -Zxz --build --root-owner-group "$ROOT" "/tmp/$PKG.deb"
|
|
||||||
echo "Built: /tmp/$PKG.deb"
|
|
||||||
dpkg-deb --info "/tmp/$PKG.deb" | head -12
|
|
||||||
|
|
|
||||||
|
|
@ -1,151 +1,146 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
# Build zupt-gui RPM. Falls back to SRPM-equivalent tarball if rpmbuild absent.
|
|
||||||
set -e
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
|
|
||||||
VERSION="${VERSION:-1.2.0}"
|
# Build a real noarch RPM and source RPM. Run this in a native RPM build
|
||||||
RPMROOT="/tmp/rpmbuild-vaptvupt-gui"
|
# environment; there is deliberately no --nodeps or tarball fallback.
|
||||||
rm -rf "$RPMROOT"
|
|
||||||
mkdir -p "$RPMROOT"/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
|
|
||||||
|
|
||||||
TMP="/tmp/vaptvupt-gui-$VERSION"
|
set -Eeuo pipefail
|
||||||
rm -rf "$TMP" && mkdir -p "$TMP/src" "$TMP/doc" "$TMP/assets"
|
umask 022
|
||||||
cp gui/src/zupt_gui.py "$TMP/src/"
|
export LC_ALL=C
|
||||||
cp doc/vaptvupt-gui.1 "$TMP/doc/" 2>/dev/null || true
|
|
||||||
cp gui/README.md "$TMP/" 2>/dev/null || true
|
|
||||||
cp LICENSE "$TMP/" 2>/dev/null || true
|
|
||||||
[ -f gui/assets/zupt-icon.png ] && cp gui/assets/zupt-icon.png "$TMP/assets/"
|
|
||||||
tar -czf "$RPMROOT/SOURCES/vaptvupt-gui-$VERSION.tar.gz" -C /tmp "vaptvupt-gui-$VERSION"
|
|
||||||
|
|
||||||
cat > "$RPMROOT/SPECS/vaptvupt-gui.spec" <<EOF
|
die() { printf 'FAIL: %s\n' "$*" >&2; exit 1; }
|
||||||
Name: vaptvupt-gui
|
|
||||||
Version: $VERSION
|
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||||
Release: 1%{?dist}
|
cd -- "$repo_root"
|
||||||
Summary: Graphical interface for VaptVupt post-quantum backup utility (formerly zupt-gui)
|
header_version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
|
version=${VERSION:-$header_version}
|
||||||
|
[[ $version == "$header_version" && $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || \
|
||||||
|
die "VERSION '$version' does not match include/zupt.h '$header_version'"
|
||||||
|
|
||||||
|
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
|
||||||
|
mkdir -p -- "$dist_dir"
|
||||||
|
dist_dir=$(cd -- "$dist_dir" && pwd -P)
|
||||||
|
case $dist_dir/ in "$repo_root"/*) die 'DIST_DIR must be outside the repository' ;; esac
|
||||||
|
|
||||||
|
for command_name in make python3 rpmbuild rpm rpm2cpio cpio tar sha256sum; do
|
||||||
|
command -v -- "$command_name" >/dev/null 2>&1 || \
|
||||||
|
die "required command not found: $command_name"
|
||||||
|
done
|
||||||
|
|
||||||
|
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')}
|
||||||
|
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-gui-rpm.XXXXXXXX")
|
||||||
|
top=$work/rpmbuild
|
||||||
|
tree=$work/zupt-gui-$version
|
||||||
|
extract=$work/extract
|
||||||
|
mkdir -p -- "$top"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} \
|
||||||
|
"$tree"/{src,assets,doc} "$extract"
|
||||||
|
cleanup() {
|
||||||
|
make -C "$repo_root" clean >/dev/null 2>&1 || true
|
||||||
|
chmod -R u+rwX "$work" 2>/dev/null || true
|
||||||
|
rm -rf -- "$work"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
|
printf '[GUI rpm] validating source-only CLI dependency %s\n' "$version"
|
||||||
|
make clean
|
||||||
|
make -j"$jobs" V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
|
||||||
|
make V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
|
||||||
|
./zupt version | grep -Fq "zupt $version" || die 'CLI version check failed'
|
||||||
|
|
||||||
|
install -m 0644 gui/src/zupt_gui.py "$tree/src/"
|
||||||
|
install -m 0644 gui/assets/zupt-icon.png "$tree/assets/"
|
||||||
|
install -m 0644 gui/packaging/zupt-gui.desktop "$tree/"
|
||||||
|
install -m 0644 doc/zupt-gui.1 "$tree/doc/"
|
||||||
|
install -m 0644 gui/README.md "$tree/README.md"
|
||||||
|
install -m 0644 LICENSE LICENSE-AGPL-3.0 "$tree/"
|
||||||
|
install -m 0644 gui/LICENSE-GUI "$tree/LICENSE-GUI"
|
||||||
|
install -m 0644 gui/assets/README.md "$tree/ASSET-PROVENANCE.md"
|
||||||
|
|
||||||
|
source_epoch=${SOURCE_DATE_EPOCH:-$(sed -n '1p' .source-date-epoch 2>/dev/null || true)}
|
||||||
|
[[ $source_epoch =~ ^[0-9]+$ ]] || die 'SOURCE_DATE_EPOCH is not available'
|
||||||
|
source_tar=$top/SOURCES/zupt-gui-$version.tar.gz
|
||||||
|
tar --sort=name --mtime="@$source_epoch" --owner=0 --group=0 --numeric-owner \
|
||||||
|
-czf "$source_tar" -C "$work" "zupt-gui-$version"
|
||||||
|
|
||||||
|
cat >"$top/SPECS/zupt-gui.spec" <<EOF
|
||||||
|
Name: zupt-gui
|
||||||
|
Version: $version
|
||||||
|
Release: 1
|
||||||
|
Summary: Qt graphical interface for the ZUPT backup utility
|
||||||
License: AGPL-3.0-or-later
|
License: AGPL-3.0-or-later
|
||||||
URL: https://git.securityops.co/cristiancmoises/zupt
|
URL: https://github.com/cristiancmoises/zupt
|
||||||
Source0: vaptvupt-gui-%{version}.tar.gz
|
Source0: %{name}-%{version}.tar.gz
|
||||||
BuildArch: noarch
|
BuildArch: noarch
|
||||||
|
|
||||||
BuildRequires: python3 >= 3.9
|
BuildRequires: python3 >= 3.9
|
||||||
Requires: python3 >= 3.9
|
Requires: python3 >= 3.9
|
||||||
Requires: (python3-qt6 or python3-pyside6 or python3-pyqt6)
|
Requires: (python3-qt6 or python3-pyside6 or python3-pyqt6)
|
||||||
Requires: (vaptvupt >= 3.0.0 or zupt >= 2.2.3)
|
Requires: zupt >= %{version}
|
||||||
Provides: zupt-gui = %{version}-%{release}
|
|
||||||
Obsoletes: zupt-gui < 1.2.0
|
|
||||||
Conflicts: zupt-gui < 1.2.0
|
|
||||||
|
|
||||||
%description
|
%description
|
||||||
PySide6/PyQt6 frontend for VaptVupt (renamed from zupt-gui in 1.x). Supports compression, extraction, key
|
ZUPT GUI creates, inspects, verifies, and extracts .zupt archives through
|
||||||
management, and full disk backup/restore. Exposes both legacy --pq and
|
the separately packaged zupt command. Optional SDK and PQ-box controls are
|
||||||
new --pq-sdk (libzuptsdk: HKDF combiner, key commitment, HPKE binding,
|
shown only when that command reports the corresponding integration enabled.
|
||||||
Argon2id) encryption modes. Auto-detects whichever Qt6 binding is
|
|
||||||
installed at startup.
|
|
||||||
|
|
||||||
%prep
|
%prep
|
||||||
%autosetup
|
%autosetup
|
||||||
|
|
||||||
%build
|
%build
|
||||||
# nothing to build; pure Python
|
|
||||||
|
%check
|
||||||
|
python3 -c 'from pathlib import Path; p=Path("src/zupt_gui.py"); compile(p.read_text(encoding="utf-8"), str(p), "exec")'
|
||||||
|
|
||||||
%install
|
%install
|
||||||
|
install -Dm0644 src/zupt_gui.py %{buildroot}%{_datadir}/zupt-gui/zupt_gui.py
|
||||||
|
install -Dm0644 zupt-gui.desktop %{buildroot}%{_datadir}/applications/zupt-gui.desktop
|
||||||
|
install -Dm0644 assets/zupt-icon.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/zupt-gui.png
|
||||||
|
install -Dm0644 doc/zupt-gui.1 %{buildroot}%{_mandir}/man1/zupt-gui.1
|
||||||
mkdir -p %{buildroot}%{_bindir}
|
mkdir -p %{buildroot}%{_bindir}
|
||||||
mkdir -p %{buildroot}%{_libdir}/vaptvupt-gui
|
cat >%{buildroot}%{_bindir}/zupt-gui <<'WRAP'
|
||||||
mkdir -p %{buildroot}%{_datadir}/applications
|
|
||||||
mkdir -p %{buildroot}%{_datadir}/icons/hicolor/256x256/apps
|
|
||||||
mkdir -p %{buildroot}%{_mandir}/man1
|
|
||||||
|
|
||||||
install -m 644 src/zupt_gui.py %{buildroot}%{_libdir}/vaptvupt-gui/
|
|
||||||
|
|
||||||
cat > %{buildroot}%{_bindir}/vaptvupt-gui <<'WRAP'
|
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
exec python3 %{_libdir}/vaptvupt-gui/zupt_gui.py "\$@"
|
exec python3 %{_datadir}/zupt-gui/zupt_gui.py "\$@"
|
||||||
WRAP
|
WRAP
|
||||||
chmod 755 %{buildroot}%{_bindir}/vaptvupt-gui
|
chmod 0755 %{buildroot}%{_bindir}/zupt-gui
|
||||||
# v3.0.0: legacy zupt-gui symlink for one major version cycle
|
|
||||||
ln -sf vaptvupt-gui %{buildroot}%{_bindir}/zupt-gui
|
|
||||||
|
|
||||||
cat > %{buildroot}%{_datadir}/applications/vaptvupt-gui.desktop <<'DESKTOP'
|
|
||||||
[Desktop Entry]
|
|
||||||
Type=Application
|
|
||||||
Name=VaptVupt GUI
|
|
||||||
GenericName=Backup and Compression Utility
|
|
||||||
Comment=Post-quantum backup with HKDF combiner, key commitment, HPKE binding
|
|
||||||
Exec=vaptvupt-gui %f
|
|
||||||
Icon=vaptvupt-gui
|
|
||||||
Terminal=false
|
|
||||||
Categories=Utility;Archiving;Compression;Security;
|
|
||||||
StartupNotify=true
|
|
||||||
DESKTOP
|
|
||||||
|
|
||||||
[ -f doc/vaptvupt-gui.1 ] && install -m 644 doc/vaptvupt-gui.1 %{buildroot}%{_mandir}/man1/
|
|
||||||
[ -f assets/zupt-icon.png ] && install -m 644 assets/zupt-icon.png %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/vaptvupt-gui.png || true
|
|
||||||
|
|
||||||
# Generate placeholder icon if no real one exists
|
|
||||||
if [ ! -f %{buildroot}%{_datadir}/icons/hicolor/256x256/apps/vaptvupt-gui.png ]; then
|
|
||||||
python3 -c "
|
|
||||||
import struct, zlib
|
|
||||||
def png(w, h, color):
|
|
||||||
raw = b''.join(b'\\0' + bytes(color) * w for _ in range(h))
|
|
||||||
def chunk(t, d): return struct.pack('>I', len(d)) + t + d + struct.pack('>I', zlib.crc32(t+d) & 0xffffffff)
|
|
||||||
return b'\\x89PNG\\r\\n\\x1a\\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0)) + chunk(b'IDAT', zlib.compress(raw)) + chunk(b'IEND', b'')
|
|
||||||
open('%{buildroot}%{_datadir}/icons/hicolor/256x256/apps/vaptvupt-gui.png','wb').write(png(256, 256, (88, 92, 215)))
|
|
||||||
"
|
|
||||||
fi
|
|
||||||
|
|
||||||
%post
|
|
||||||
if [ -x /usr/bin/update-desktop-database ]; then
|
|
||||||
update-desktop-database -q /usr/share/applications || :
|
|
||||||
fi
|
|
||||||
if [ -x /usr/bin/gtk-update-icon-cache ]; then
|
|
||||||
gtk-update-icon-cache -q /usr/share/icons/hicolor || :
|
|
||||||
fi
|
|
||||||
|
|
||||||
%postun
|
|
||||||
if [ \$1 -eq 0 ]; then
|
|
||||||
if [ -x /usr/bin/update-desktop-database ]; then
|
|
||||||
update-desktop-database -q /usr/share/applications || :
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
%files
|
%files
|
||||||
%doc README.md
|
%license LICENSE LICENSE-AGPL-3.0 LICENSE-GUI
|
||||||
%license LICENSE
|
%doc README.md ASSET-PROVENANCE.md
|
||||||
%{_bindir}/vaptvupt-gui
|
|
||||||
%{_bindir}/zupt-gui
|
%{_bindir}/zupt-gui
|
||||||
%{_libdir}/vaptvupt-gui/zupt_gui.py
|
%{_datadir}/zupt-gui/zupt_gui.py
|
||||||
%{_datadir}/applications/vaptvupt-gui.desktop
|
%{_datadir}/applications/zupt-gui.desktop
|
||||||
%{_datadir}/icons/hicolor/256x256/apps/vaptvupt-gui.png
|
%{_datadir}/icons/hicolor/256x256/apps/zupt-gui.png
|
||||||
|
%{_mandir}/man1/zupt-gui.1*
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
* Sun May 25 2026 Cristian Cezar Moisés <zupt@riseup.net> - $VERSION-1
|
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - $version-1
|
||||||
- v1.2.0: package renamed zupt-gui → vaptvupt-gui (parent CLI also
|
- Package the integrated GUI under its restored ZUPT identity.
|
||||||
renamed; INPI Brasil trademark on "Zupt"). Legacy /usr/bin/zupt-gui
|
- Require the separately built source-only baseline CLI package.
|
||||||
symlink preserved. GUI binary-discovery bug fix: _find_vaptvupt
|
|
||||||
with liveness check + discovery log via VAPTVUPT_DEBUG=1.
|
|
||||||
* Mon Apr 27 2026 Cristian Cezar Moisés <zupt@riseup.net> - 1.1.1-1
|
|
||||||
- Cross-binding (PySide6 OR PyQt6 auto-detected)
|
|
||||||
- SDK v2 mode toggles in compress/extract/keygen tabs
|
|
||||||
- Man page added
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
if command -v rpmbuild >/dev/null 2>&1; then
|
rpmbuild --define "_topdir $top" -ba "$top/SPECS/zupt-gui.spec"
|
||||||
# On Debian/Ubuntu, the host's `rpm` doesn't see `python3` as an RPM
|
|
||||||
# (it's a deb), so the BuildRequires check would fail. Use --nodeps
|
mapfile -t main_rpms < <(find "$top/RPMS" -type f -name "zupt-gui-$version-*.noarch.rpm" -print | sort)
|
||||||
# since the runtime check on the target system is what actually
|
mapfile -t source_rpms < <(find "$top/SRPMS" -type f -name "zupt-gui-$version-*.src.rpm" -print | sort)
|
||||||
# matters. The Requires: lines still apply on install.
|
[[ ${#main_rpms[@]} -eq 1 ]] || die "expected one GUI RPM, found ${#main_rpms[@]}"
|
||||||
rpmbuild --define "_topdir $RPMROOT" --nodeps -bb "$RPMROOT/SPECS/vaptvupt-gui.spec" 2>&1 | tail -3
|
[[ ${#source_rpms[@]} -eq 1 ]] || die "expected one GUI source RPM, found ${#source_rpms[@]}"
|
||||||
if [ -f "$RPMROOT/RPMS/noarch/vaptvupt-gui-${VERSION}-1.noarch.rpm" ]; then
|
|
||||||
cp "$RPMROOT/RPMS/noarch/vaptvupt-gui-${VERSION}-1.noarch.rpm" \
|
rpm -qpl "${main_rpms[0]}" >"$work/contents.txt"
|
||||||
"/tmp/vaptvupt-gui-${VERSION}-1.noarch.rpm"
|
grep -q '^/usr/bin/zupt-gui$' "$work/contents.txt" || die 'GUI launcher missing from RPM'
|
||||||
echo "Built: /tmp/vaptvupt-gui-${VERSION}-1.noarch.rpm"
|
if grep -Eq '(^/usr/bin/vaptvupt-gui$|\.(o|obj|a|so|so\.[^/]+|dll|dylib|exe)$)' "$work/contents.txt"; then
|
||||||
fi
|
cat "$work/contents.txt" >&2
|
||||||
cp "$RPMROOT/RPMS/noarch/zupt-gui-$VERSION-1."*.rpm /tmp/ 2>/dev/null || true
|
die 'forbidden compatibility alias or compiled artifact in GUI RPM'
|
||||||
ls /tmp/zupt-gui-$VERSION-*.rpm 2>/dev/null
|
|
||||||
else
|
|
||||||
SRPM_TAR="/tmp/zupt-gui-$VERSION.srpm.tar.gz"
|
|
||||||
tar -czf "$SRPM_TAR" -C "$RPMROOT" SPECS SOURCES
|
|
||||||
echo "rpmbuild unavailable; SRPM-equivalent at: $SRPM_TAR"
|
|
||||||
fi
|
fi
|
||||||
|
(cd -- "$extract" && rpm2cpio "${main_rpms[0]}" | cpio -idm --quiet)
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 python3 - <<PY
|
||||||
|
from pathlib import Path
|
||||||
|
p = Path("$extract/usr/share/zupt-gui/zupt_gui.py")
|
||||||
|
compile(p.read_text(encoding="utf-8"), str(p), "exec")
|
||||||
|
PY
|
||||||
|
|
||||||
|
for artifact in "${main_rpms[0]}" "${source_rpms[0]}"; do
|
||||||
|
destination=$dist_dir/$(basename -- "$artifact")
|
||||||
|
[[ ! -e $destination ]] || die "refusing to overwrite existing output: $destination"
|
||||||
|
cp -- "$artifact" "$destination"
|
||||||
|
sha256sum "$destination"
|
||||||
|
done
|
||||||
|
printf 'PASS: built and content-validated GUI RPM and source RPM in %s\n' "$dist_dir"
|
||||||
|
|
|
||||||
|
|
@ -1,339 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
|
||||||
"""
|
|
||||||
Build a binary RPM for zupt without rpmbuild.
|
|
||||||
Constructs an RPM-format file directly from the file tree we have for deb.
|
|
||||||
|
|
||||||
This is intentionally minimal but produces a valid RPM that:
|
|
||||||
- Can be installed via `rpm -i` on RHEL/Fedora and other RPM-based distributions
|
|
||||||
- Contains correct dependency info
|
|
||||||
- Has working pre/post scripts
|
|
||||||
- Includes the binary, library, headers, docs, license
|
|
||||||
"""
|
|
||||||
import struct, os, sys, hashlib, gzip, io, time, subprocess
|
|
||||||
|
|
||||||
VERSION = os.environ.get('VERSION', '2.2.3')
|
|
||||||
RELEASE = '1'
|
|
||||||
ARCH = 'x86_64'
|
|
||||||
NAME = 'zupt'
|
|
||||||
|
|
||||||
# RPM tag values (from rpmtag.h)
|
|
||||||
RPMTAG_NAME = 1000
|
|
||||||
RPMTAG_VERSION = 1001
|
|
||||||
RPMTAG_RELEASE = 1002
|
|
||||||
RPMTAG_SUMMARY = 1004
|
|
||||||
RPMTAG_DESCRIPTION = 1005
|
|
||||||
RPMTAG_BUILDTIME = 1006
|
|
||||||
RPMTAG_BUILDHOST = 1007
|
|
||||||
RPMTAG_SIZE = 1009
|
|
||||||
RPMTAG_DISTRIBUTION = 1010
|
|
||||||
RPMTAG_VENDOR = 1011
|
|
||||||
RPMTAG_LICENSE = 1014
|
|
||||||
RPMTAG_PACKAGER = 1015
|
|
||||||
RPMTAG_GROUP = 1016
|
|
||||||
RPMTAG_URL = 1020
|
|
||||||
RPMTAG_OS = 1021
|
|
||||||
RPMTAG_ARCH = 1022
|
|
||||||
RPMTAG_PREIN = 1023
|
|
||||||
RPMTAG_POSTIN = 1024
|
|
||||||
RPMTAG_PREUN = 1025
|
|
||||||
RPMTAG_POSTUN = 1026
|
|
||||||
RPMTAG_FILESIZES = 1028
|
|
||||||
RPMTAG_FILEMODES = 1030
|
|
||||||
RPMTAG_FILERDEVS = 1033
|
|
||||||
RPMTAG_FILEMTIMES = 1034
|
|
||||||
RPMTAG_FILEDIGESTS = 1035
|
|
||||||
RPMTAG_FILELINKTOS = 1036
|
|
||||||
RPMTAG_FILEFLAGS = 1037
|
|
||||||
RPMTAG_FILEUSERNAME = 1039
|
|
||||||
RPMTAG_FILEGROUPNAME = 1040
|
|
||||||
RPMTAG_PROVIDENAME = 1047
|
|
||||||
RPMTAG_REQUIREFLAGS = 1048
|
|
||||||
RPMTAG_REQUIRENAME = 1049
|
|
||||||
RPMTAG_REQUIREVERSION = 1050
|
|
||||||
RPMTAG_BASENAMES = 1117
|
|
||||||
RPMTAG_DIRNAMES = 1118
|
|
||||||
RPMTAG_DIRINDEXES = 1116
|
|
||||||
RPMTAG_PAYLOADFORMAT = 1124
|
|
||||||
RPMTAG_PAYLOADCOMPRESSOR = 1125
|
|
||||||
RPMTAG_FILEDIGESTALGO = 5011
|
|
||||||
|
|
||||||
# Type codes
|
|
||||||
RPM_NULL_TYPE = 0
|
|
||||||
RPM_CHAR_TYPE = 1
|
|
||||||
RPM_INT8_TYPE = 2
|
|
||||||
RPM_INT16_TYPE = 3
|
|
||||||
RPM_INT32_TYPE = 4
|
|
||||||
RPM_INT64_TYPE = 5
|
|
||||||
RPM_STRING_TYPE = 6
|
|
||||||
RPM_BIN_TYPE = 7
|
|
||||||
RPM_STRING_ARRAY_TYPE = 8
|
|
||||||
|
|
||||||
class Header:
|
|
||||||
def __init__(self):
|
|
||||||
self.entries = [] # (tag, type, value)
|
|
||||||
|
|
||||||
def add(self, tag, typ, value):
|
|
||||||
self.entries.append((tag, typ, value))
|
|
||||||
|
|
||||||
def serialize(self):
|
|
||||||
# Build store + index
|
|
||||||
store = bytearray()
|
|
||||||
index = []
|
|
||||||
for tag, typ, value in self.entries:
|
|
||||||
if typ == RPM_STRING_TYPE:
|
|
||||||
count = 1
|
|
||||||
data = value.encode('utf-8') + b'\x00'
|
|
||||||
offset = len(store)
|
|
||||||
store.extend(data)
|
|
||||||
elif typ == RPM_STRING_ARRAY_TYPE:
|
|
||||||
count = len(value)
|
|
||||||
data = b''.join(s.encode('utf-8') + b'\x00' for s in value)
|
|
||||||
offset = len(store)
|
|
||||||
store.extend(data)
|
|
||||||
elif typ == RPM_INT32_TYPE:
|
|
||||||
if not isinstance(value, list):
|
|
||||||
value = [value]
|
|
||||||
count = len(value)
|
|
||||||
# align to 4
|
|
||||||
while len(store) % 4: store.append(0)
|
|
||||||
offset = len(store)
|
|
||||||
for v in value:
|
|
||||||
store.extend(struct.pack('>I', v & 0xFFFFFFFF))
|
|
||||||
elif typ == RPM_INT16_TYPE:
|
|
||||||
if not isinstance(value, list):
|
|
||||||
value = [value]
|
|
||||||
count = len(value)
|
|
||||||
while len(store) % 2: store.append(0)
|
|
||||||
offset = len(store)
|
|
||||||
for v in value:
|
|
||||||
store.extend(struct.pack('>H', v & 0xFFFF))
|
|
||||||
elif typ == RPM_BIN_TYPE:
|
|
||||||
count = len(value)
|
|
||||||
offset = len(store)
|
|
||||||
store.extend(value)
|
|
||||||
elif typ == RPM_NULL_TYPE:
|
|
||||||
count = 1
|
|
||||||
offset = 0
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Unsupported type {typ}")
|
|
||||||
index.append(struct.pack('>IIII', tag, typ, offset, count))
|
|
||||||
|
|
||||||
index_bytes = b''.join(index)
|
|
||||||
# Header magic + reserved + index count + store size
|
|
||||||
out = struct.pack('>3sBI4sII', b'\x8e\xad\xe8', 1, 0, b'\x00\x00\x00\x00',
|
|
||||||
len(self.entries), len(store))
|
|
||||||
out += index_bytes + bytes(store)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def make_cpio(file_list, source_root, payload_size_out):
|
|
||||||
"""Build a cpio archive (newc format) of the files."""
|
|
||||||
out = io.BytesIO()
|
|
||||||
inode = 1
|
|
||||||
total = 0
|
|
||||||
for arc_path, src_path, mode, is_dir, link_target in file_list:
|
|
||||||
if is_dir:
|
|
||||||
data = b''
|
|
||||||
file_size = 0
|
|
||||||
elif link_target is not None:
|
|
||||||
data = link_target.encode('utf-8')
|
|
||||||
file_size = len(data)
|
|
||||||
else:
|
|
||||||
with open(src_path, 'rb') as f:
|
|
||||||
data = f.read()
|
|
||||||
file_size = len(data)
|
|
||||||
total += file_size
|
|
||||||
|
|
||||||
name = ('.' + arc_path).encode('utf-8') + b'\x00'
|
|
||||||
# newc header: 110 bytes
|
|
||||||
header = (
|
|
||||||
b'070701'
|
|
||||||
+ format(inode, '08x').encode('ascii')
|
|
||||||
+ format(mode, '08x').encode('ascii')
|
|
||||||
+ b'00000000' # uid
|
|
||||||
+ b'00000000' # gid
|
|
||||||
+ b'00000001' # nlink
|
|
||||||
+ format(int(time.time()), '08x').encode('ascii')
|
|
||||||
+ format(file_size, '08x').encode('ascii')
|
|
||||||
+ b'00000000' * 4 # devmajor/minor + rdevmajor/minor
|
|
||||||
+ format(len(name), '08x').encode('ascii')
|
|
||||||
+ b'00000000' # check
|
|
||||||
)
|
|
||||||
out.write(header)
|
|
||||||
out.write(name)
|
|
||||||
# pad to 4
|
|
||||||
pad = (4 - ((len(header) + len(name)) % 4)) % 4
|
|
||||||
out.write(b'\x00' * pad)
|
|
||||||
out.write(data)
|
|
||||||
# pad data to 4
|
|
||||||
pad = (4 - (file_size % 4)) % 4
|
|
||||||
out.write(b'\x00' * pad)
|
|
||||||
inode += 1
|
|
||||||
|
|
||||||
# Trailer
|
|
||||||
trailer_name = b'TRAILER!!!\x00'
|
|
||||||
out.write(b'070701' + b'0' * 8 + b'0' * 8 + b'0' * 8 + b'0' * 8 + b'00000001'
|
|
||||||
+ b'0' * 8 + b'0' * 8 + b'0' * 8 + b'0' * 8 + b'0' * 8 + b'0' * 8
|
|
||||||
+ format(len(trailer_name), '08x').encode('ascii') + b'0' * 8)
|
|
||||||
out.write(trailer_name)
|
|
||||||
pad = (4 - ((110 + len(trailer_name)) % 4)) % 4
|
|
||||||
out.write(b'\x00' * pad)
|
|
||||||
payload_size_out[0] = total
|
|
||||||
return out.getvalue()
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Files to include (source_path inside our deb tree)
|
|
||||||
deb_root = f'/tmp/zupt_{VERSION}_amd64'
|
|
||||||
files = [] # (arc_path, source_path, mode, is_dir, link_target)
|
|
||||||
|
|
||||||
for root, dirs, fnames in os.walk(deb_root):
|
|
||||||
for d in sorted(dirs):
|
|
||||||
full = os.path.join(root, d)
|
|
||||||
arc = full[len(deb_root):]
|
|
||||||
files.append((arc, full, 0o40755, True, None))
|
|
||||||
for fn in sorted(fnames):
|
|
||||||
full = os.path.join(root, fn)
|
|
||||||
arc = full[len(deb_root):]
|
|
||||||
if 'DEBIAN' in arc:
|
|
||||||
continue
|
|
||||||
if os.path.islink(full):
|
|
||||||
files.append((arc, full, 0o120777, False, os.readlink(full)))
|
|
||||||
else:
|
|
||||||
mode = 0o100755 if os.access(full, os.X_OK) else 0o100644
|
|
||||||
files.append((arc, full, mode, False, None))
|
|
||||||
|
|
||||||
# Sort and build basename/dirname/dirindex arrays
|
|
||||||
files.sort(key=lambda x: x[0])
|
|
||||||
|
|
||||||
basenames = []
|
|
||||||
dirnames_set = []
|
|
||||||
dirname_to_idx = {}
|
|
||||||
dirindexes = []
|
|
||||||
filesizes = []
|
|
||||||
filemodes = []
|
|
||||||
filemtimes = []
|
|
||||||
filedigests = []
|
|
||||||
filelinktos = []
|
|
||||||
filerdevs = []
|
|
||||||
fileflags = []
|
|
||||||
fileuser = []
|
|
||||||
filegroup = []
|
|
||||||
|
|
||||||
for arc, src, mode, is_dir, link in files:
|
|
||||||
d, b = os.path.split(arc)
|
|
||||||
d = d + '/'
|
|
||||||
if d not in dirname_to_idx:
|
|
||||||
dirname_to_idx[d] = len(dirnames_set)
|
|
||||||
dirnames_set.append(d)
|
|
||||||
basenames.append(b or '.')
|
|
||||||
dirindexes.append(dirname_to_idx[d])
|
|
||||||
if is_dir:
|
|
||||||
filesizes.append(0)
|
|
||||||
filedigests.append('')
|
|
||||||
filelinktos.append('')
|
|
||||||
elif link:
|
|
||||||
filesizes.append(len(link))
|
|
||||||
filedigests.append('')
|
|
||||||
filelinktos.append(link)
|
|
||||||
else:
|
|
||||||
filesizes.append(os.path.getsize(src))
|
|
||||||
with open(src, 'rb') as f:
|
|
||||||
filedigests.append(hashlib.sha256(f.read()).hexdigest())
|
|
||||||
filelinktos.append('')
|
|
||||||
filemodes.append(mode)
|
|
||||||
filemtimes.append(int(time.time()))
|
|
||||||
filerdevs.append(0)
|
|
||||||
fileflags.append(0)
|
|
||||||
fileuser.append('root')
|
|
||||||
filegroup.append('root')
|
|
||||||
|
|
||||||
payload_size = [0]
|
|
||||||
cpio_data = make_cpio(files, deb_root, payload_size)
|
|
||||||
# Compress payload with gzip
|
|
||||||
gz_payload = gzip.compress(cpio_data)
|
|
||||||
|
|
||||||
# Build main header
|
|
||||||
h = Header()
|
|
||||||
h.add(RPMTAG_NAME, RPM_STRING_TYPE, NAME)
|
|
||||||
h.add(RPMTAG_VERSION, RPM_STRING_TYPE, VERSION)
|
|
||||||
h.add(RPMTAG_RELEASE, RPM_STRING_TYPE, RELEASE)
|
|
||||||
h.add(RPMTAG_SUMMARY, RPM_STRING_ARRAY_TYPE, ['Post-quantum backup compression utility'])
|
|
||||||
h.add(RPMTAG_DESCRIPTION, RPM_STRING_ARRAY_TYPE, [
|
|
||||||
'Zupt provides hybrid post-quantum encryption (ML-KEM-768 + X25519)\n'
|
|
||||||
'with multi-threaded compression and full-disk backup support.\n'
|
|
||||||
'Bundled with libzuptsdk for HKDF-SHA3 hybrid KDF, key commitment,\n'
|
|
||||||
'HPKE binding, and anti-fault decapsulation.'
|
|
||||||
])
|
|
||||||
h.add(RPMTAG_BUILDTIME, RPM_INT32_TYPE, int(time.time()))
|
|
||||||
h.add(RPMTAG_BUILDHOST, RPM_STRING_TYPE, 'localhost')
|
|
||||||
h.add(RPMTAG_SIZE, RPM_INT32_TYPE, sum(filesizes))
|
|
||||||
h.add(RPMTAG_LICENSE, RPM_STRING_TYPE, 'AGPL-3.0-or-later')
|
|
||||||
h.add(RPMTAG_PACKAGER, RPM_STRING_TYPE, 'Cristian Cezar Moises <zupt@riseup.net>')
|
|
||||||
h.add(RPMTAG_GROUP, RPM_STRING_ARRAY_TYPE, ['Applications/Archiving'])
|
|
||||||
h.add(RPMTAG_URL, RPM_STRING_TYPE, 'https://git.securityops.co/cristiancmoises/zupt')
|
|
||||||
h.add(RPMTAG_OS, RPM_STRING_TYPE, 'linux')
|
|
||||||
h.add(RPMTAG_ARCH, RPM_STRING_TYPE, ARCH)
|
|
||||||
h.add(RPMTAG_POSTIN, RPM_STRING_TYPE, '/sbin/ldconfig\n')
|
|
||||||
h.add(RPMTAG_POSTUN, RPM_STRING_TYPE, '/sbin/ldconfig\n')
|
|
||||||
h.add(RPMTAG_BASENAMES, RPM_STRING_ARRAY_TYPE, basenames)
|
|
||||||
h.add(RPMTAG_DIRNAMES, RPM_STRING_ARRAY_TYPE, dirnames_set)
|
|
||||||
h.add(RPMTAG_DIRINDEXES, RPM_INT32_TYPE, dirindexes)
|
|
||||||
h.add(RPMTAG_FILESIZES, RPM_INT32_TYPE, filesizes)
|
|
||||||
h.add(RPMTAG_FILEMODES, RPM_INT16_TYPE, filemodes)
|
|
||||||
h.add(RPMTAG_FILEMTIMES, RPM_INT32_TYPE, filemtimes)
|
|
||||||
h.add(RPMTAG_FILEDIGESTS, RPM_STRING_ARRAY_TYPE, filedigests)
|
|
||||||
h.add(RPMTAG_FILELINKTOS, RPM_STRING_ARRAY_TYPE, filelinktos)
|
|
||||||
h.add(RPMTAG_FILEFLAGS, RPM_INT32_TYPE, fileflags)
|
|
||||||
h.add(RPMTAG_FILERDEVS, RPM_INT16_TYPE, filerdevs)
|
|
||||||
h.add(RPMTAG_FILEUSERNAME, RPM_STRING_ARRAY_TYPE, fileuser)
|
|
||||||
h.add(RPMTAG_FILEGROUPNAME, RPM_STRING_ARRAY_TYPE, filegroup)
|
|
||||||
h.add(RPMTAG_PROVIDENAME, RPM_STRING_ARRAY_TYPE, [NAME])
|
|
||||||
h.add(RPMTAG_REQUIRENAME, RPM_STRING_ARRAY_TYPE, ['libargon2.so.1()(64bit)', 'libcrypto.so.3()(64bit)', 'libc.so.6()(64bit)'])
|
|
||||||
h.add(RPMTAG_REQUIREFLAGS, RPM_INT32_TYPE, [0, 0, 0])
|
|
||||||
h.add(RPMTAG_REQUIREVERSION, RPM_STRING_ARRAY_TYPE, ['', '', ''])
|
|
||||||
h.add(RPMTAG_PAYLOADFORMAT, RPM_STRING_TYPE, 'cpio')
|
|
||||||
h.add(RPMTAG_PAYLOADCOMPRESSOR, RPM_STRING_TYPE, 'gzip')
|
|
||||||
h.add(RPMTAG_FILEDIGESTALGO, RPM_INT32_TYPE, 8) # SHA-256
|
|
||||||
|
|
||||||
main_hdr = h.serialize()
|
|
||||||
|
|
||||||
# Signature header (minimal: just size of payload after sig hdr)
|
|
||||||
sig = Header()
|
|
||||||
sig_payload = main_hdr + gz_payload
|
|
||||||
sig.add(1000, RPM_INT32_TYPE, len(sig_payload)) # SIZE
|
|
||||||
sig.add(1004, RPM_BIN_TYPE, hashlib.md5(sig_payload).digest()) # MD5
|
|
||||||
sig_bytes = sig.serialize()
|
|
||||||
# Pad sig hdr to 8-byte boundary
|
|
||||||
pad = (8 - (len(sig_bytes) % 8)) % 8
|
|
||||||
sig_bytes += b'\x00' * pad
|
|
||||||
|
|
||||||
# Lead (96 bytes)
|
|
||||||
lead = struct.pack('>4sBBhh66sHH16s',
|
|
||||||
b'\xed\xab\xee\xdb', # magic
|
|
||||||
3, 0, # major, minor
|
|
||||||
0, # type (binary)
|
|
||||||
1, # archnum
|
|
||||||
NAME.encode().ljust(66, b'\x00'),
|
|
||||||
1, # osnum
|
|
||||||
5, # signature_type
|
|
||||||
b'\x00' * 16)
|
|
||||||
|
|
||||||
out_path = f'/tmp/{NAME}-{VERSION}-{RELEASE}.{ARCH}.rpm'
|
|
||||||
with open(out_path, 'wb') as f:
|
|
||||||
f.write(lead)
|
|
||||||
f.write(sig_bytes)
|
|
||||||
f.write(main_hdr)
|
|
||||||
f.write(gz_payload)
|
|
||||||
|
|
||||||
print(f'Built: {out_path} ({os.path.getsize(out_path)} bytes)')
|
|
||||||
# Try rpm -Kvv to verify if rpm is installed
|
|
||||||
try:
|
|
||||||
result = subprocess.run(['rpm', '-qpi', out_path], capture_output=True, text=True, timeout=5)
|
|
||||||
if result.returncode == 0:
|
|
||||||
print(result.stdout[:500])
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,195 +1,127 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
#
|
|
||||||
# Build self-contained vaptvupt RPM (formerly zupt). Bundles
|
|
||||||
# libzuptsdk.so.2 under /usr/lib/vaptvupt/ so users do NOT need
|
|
||||||
# a separate libzuptsdk package. Installs a legacy /usr/bin/zupt
|
|
||||||
# symlink for one major version cycle.
|
|
||||||
|
|
||||||
set -e
|
set -Eeuo pipefail
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
|
|
||||||
VERSION="${VERSION:-3.0.0}"
|
umask 022
|
||||||
ARCH="${ARCH:-x86_64}"
|
export LC_ALL=C
|
||||||
RELEASE="1"
|
|
||||||
PKGNAME="vaptvupt"
|
|
||||||
LEGACY="zupt"
|
|
||||||
|
|
||||||
SDK_LIB="vendor/zuptsdk/libzuptsdk.so.2.0.0"
|
die() {
|
||||||
if [ ! -f "$SDK_LIB" ]; then
|
printf 'FAIL: %s\n' "$*" >&2
|
||||||
echo "ERROR: $SDK_LIB not found." >&2
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
}
|
||||||
|
|
||||||
echo "[rpm] Building $PKGNAME"
|
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||||
make clean >/dev/null 2>&1 || true
|
cd -- "$repo_root"
|
||||||
make -j"$(nproc)" >/dev/null
|
|
||||||
echo "[rpm] Patching rpath -> /usr/lib/$PKGNAME:/usr/lib64/$PKGNAME"
|
|
||||||
patchelf --set-rpath "/usr/lib/$PKGNAME:/usr/lib64/$PKGNAME" $PKGNAME
|
|
||||||
if ! readelf -d $PKGNAME | grep -q "RUNPATH.*\[/usr/lib/$PKGNAME:/usr/lib64/$PKGNAME\]"; then
|
|
||||||
echo "ERROR: $PKGNAME does not have correct RUNPATH" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v rpmbuild >/dev/null 2>&1; then
|
header_version=$(sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p' include/zupt.h)
|
||||||
echo "[rpm] rpmbuild not found; install rpm package to proceed"
|
version=${VERSION:-$header_version}
|
||||||
exit 1
|
[[ -n $version && $version == "$header_version" ]] || \
|
||||||
fi
|
die "VERSION '$version' does not match include/zupt.h '$header_version'"
|
||||||
|
[[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "invalid package version: $version"
|
||||||
|
|
||||||
RPMROOT="/tmp/rpmbuild-$PKGNAME"
|
spec=packaging/opensuse/zupt.spec
|
||||||
rm -rf "$RPMROOT"
|
[[ -f $spec ]] || die "spec file not found: $spec"
|
||||||
mkdir -p "$RPMROOT"/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
|
spec_version=$(sed -n 's/^Version:[[:space:]]*//p' "$spec" | head -n 1)
|
||||||
|
[[ $spec_version == "$version" ]] || die "spec version '$spec_version' does not match '$version'"
|
||||||
|
|
||||||
STAGE="/tmp/$PKGNAME-rpm-stage-${VERSION}"
|
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
|
||||||
rm -rf "$STAGE"
|
mkdir -p -- "$dist_dir"
|
||||||
mkdir -p "$STAGE/$PKGNAME-${VERSION}/completions"
|
dist_dir=$(cd -- "$dist_dir" && pwd -P)
|
||||||
|
|
||||||
cp $PKGNAME "$STAGE/$PKGNAME-${VERSION}/$PKGNAME"
|
for command_name in make git rpmbuild rpm rpm2cpio cpio date readelf sha256sum tar; do
|
||||||
cp "$SDK_LIB" "$STAGE/$PKGNAME-${VERSION}/libzuptsdk.so.2.0.0"
|
command -v -- "$command_name" >/dev/null 2>&1 || die "required command not found: $command_name"
|
||||||
cp "vendor/pqvaptvupt/libpqvaptvupt.so.0.6.0" "$STAGE/$PKGNAME-${VERSION}/libpqvaptvupt.so.0.6.0"
|
done
|
||||||
cp README.md CHANGELOG.md SECURITY.md AUDIT.md LICENSE "$STAGE/$PKGNAME-${VERSION}/"
|
|
||||||
[ -f doc/vaptvupt.1 ] && cp doc/vaptvupt.1 "$STAGE/$PKGNAME-${VERSION}/$PKGNAME.1"
|
|
||||||
[ -f completions/vaptvupt.bash ] && cp completions/vaptvupt.bash "$STAGE/$PKGNAME-${VERSION}/completions/"
|
|
||||||
[ -f completions/_vaptvupt ] && cp completions/_vaptvupt "$STAGE/$PKGNAME-${VERSION}/completions/"
|
|
||||||
[ -f completions/vaptvupt.fish ] && cp completions/vaptvupt.fish "$STAGE/$PKGNAME-${VERSION}/completions/"
|
|
||||||
tar -czf "$RPMROOT/SOURCES/$PKGNAME-${VERSION}.tar.gz" -C "$STAGE" "$PKGNAME-${VERSION}"
|
|
||||||
|
|
||||||
cat > "$RPMROOT/SPECS/$PKGNAME.spec" <<EOF
|
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-rpm.XXXXXXXX")
|
||||||
Name: $PKGNAME
|
top=$work/rpmbuild
|
||||||
Version: $VERSION
|
extract=$work/extract
|
||||||
Release: ${RELEASE}%{?dist}
|
mkdir -p -- "$top/BUILD" "$top/BUILDROOT" "$top/RPMS" "$top/SOURCES" \
|
||||||
Summary: Post-quantum backup compression utility (formerly zupt)
|
"$top/SPECS" "$top/SRPMS" "$extract"
|
||||||
License: AGPL-3.0-or-later AND GPL-3.0-or-later
|
|
||||||
URL: https://git.securityops.co/cristiancmoises/zupt
|
|
||||||
Source0: $PKGNAME-%{version}.tar.gz
|
|
||||||
|
|
||||||
# v3.0.0 rename — INPI Brasil trademark on the prior name "Zupt".
|
cleanup() {
|
||||||
# Cleanly supersede legacy 'zupt' RPMs.
|
chmod -R u+rwX "$work" 2>/dev/null || true
|
||||||
Provides: $LEGACY = %{version}-%{release}
|
rm -rf -- "$work"
|
||||||
Obsoletes: $LEGACY < 3.0.0
|
}
|
||||||
Conflicts: $LEGACY < 3.0.0
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
Requires: libargon2
|
source_tar=$top/SOURCES/zupt-${version}.tar.gz
|
||||||
Requires: openssl-libs >= 3.0
|
printf '[rpm] creating audited source archive for ZUPT %s\n' "$version"
|
||||||
AutoReqProv: no
|
make DIST_TARBALL="$source_tar" WITH_SDK=0 WITH_PQBOX=0 dist
|
||||||
|
archive_version=$(tar -xOf "$source_tar" "zupt-${version}/include/zupt.h" | \
|
||||||
|
sed -n 's/^#define ZUPT_VERSION_STRING "\([^"]*\)".*/\1/p')
|
||||||
|
[[ $archive_version == "$version" ]] || die "source archive version is '$archive_version', expected '$version'"
|
||||||
|
|
||||||
%global debug_package %{nil}
|
install -m 0644 "$spec" "$top/SPECS/zupt.spec"
|
||||||
%global __os_install_post %{nil}
|
# OBS converts zupt.changes into RPM changelog metadata. Standalone
|
||||||
%global _build_id_links none
|
# rpmbuild does not, so add an equivalent release entry only to the temporary
|
||||||
|
# spec used for this release artifact.
|
||||||
|
changelog_sections=$(grep -Ec '^%changelog[[:space:]]*$' "$top/SPECS/zupt.spec" || true)
|
||||||
|
[[ $changelog_sections -eq 1 ]] || \
|
||||||
|
die "expected exactly one %changelog section, found $changelog_sections"
|
||||||
|
source_epoch=${SOURCE_DATE_EPOCH:-$(sed -n '1p' .source-date-epoch 2>/dev/null || true)}
|
||||||
|
[[ $source_epoch =~ ^[0-9]+$ ]] || die 'SOURCE_DATE_EPOCH is not available'
|
||||||
|
changelog_date=$(date -u --date="@$source_epoch" '+%a %b %d %Y')
|
||||||
|
cat >> "$top/SPECS/zupt.spec" <<EOF
|
||||||
|
|
||||||
%description
|
* $changelog_date Cristian Cezar Moisés <sac@securityops.co> - $version-0
|
||||||
VaptVupt (renamed from Zupt in v3.0.0 due to INPI Brasil trademark
|
- Build the release package from audited source with optional SDK and PQBOX
|
||||||
on the prior name) is a backup-oriented compression utility with
|
features disabled.
|
||||||
hybrid post-quantum encryption (ML-KEM-768 + X25519). Provides
|
|
||||||
AES-256-CTR + HMAC-SHA256 authenticated encryption, multi-threaded
|
|
||||||
compression, full-disk backup/restore, block-level deduplication,
|
|
||||||
and embeds the VaptVupt 2.48.5 LZ + ANS codec with AVX2 and NEON
|
|
||||||
SIMD acceleration. The libzuptsdk shared library is bundled under
|
|
||||||
/usr/lib/$PKGNAME -- no separate package required.
|
|
||||||
|
|
||||||
The on-disk archive extension is unchanged (.zupt); v2.x and v3.0.0
|
|
||||||
archives are bidirectionally compatible. The legacy /usr/bin/zupt
|
|
||||||
symlink is preserved for one major version cycle.
|
|
||||||
|
|
||||||
%prep
|
|
||||||
%setup -q
|
|
||||||
|
|
||||||
%build
|
|
||||||
# Pre-built before rpmbuild was invoked; nothing to do.
|
|
||||||
|
|
||||||
%install
|
|
||||||
mkdir -p %{buildroot}%{_bindir}
|
|
||||||
mkdir -p %{buildroot}%{_libdir}/$PKGNAME
|
|
||||||
mkdir -p %{buildroot}%{_docdir}/$PKGNAME
|
|
||||||
mkdir -p %{buildroot}%{_licensedir}/$PKGNAME
|
|
||||||
mkdir -p %{buildroot}%{_mandir}/man1
|
|
||||||
mkdir -p %{buildroot}%{_datadir}/bash-completion/completions
|
|
||||||
mkdir -p %{buildroot}%{_datadir}/zsh/site-functions
|
|
||||||
mkdir -p %{buildroot}%{_datadir}/fish/vendor_completions.d
|
|
||||||
|
|
||||||
install -m 755 $PKGNAME %{buildroot}%{_bindir}/$PKGNAME
|
|
||||||
ln -sf $PKGNAME %{buildroot}%{_bindir}/$LEGACY
|
|
||||||
|
|
||||||
install -m 755 libzuptsdk.so.2.0.0 %{buildroot}%{_libdir}/$PKGNAME/libzuptsdk.so.2.0.0
|
|
||||||
ln -sf libzuptsdk.so.2.0.0 %{buildroot}%{_libdir}/$PKGNAME/libzuptsdk.so.2
|
|
||||||
ln -sf libzuptsdk.so.2.0.0 %{buildroot}%{_libdir}/$PKGNAME/libzuptsdk.so
|
|
||||||
install -m 755 libpqvaptvupt.so.0.6.0 %{buildroot}%{_libdir}/$PKGNAME/libpqvaptvupt.so.0.6.0
|
|
||||||
ln -sf libpqvaptvupt.so.0.6.0 %{buildroot}%{_libdir}/$PKGNAME/libpqvaptvupt.so.0
|
|
||||||
ln -sf libpqvaptvupt.so.0.6.0 %{buildroot}%{_libdir}/$PKGNAME/libpqvaptvupt.so
|
|
||||||
|
|
||||||
install -m 644 README.md CHANGELOG.md SECURITY.md AUDIT.md %{buildroot}%{_docdir}/$PKGNAME/
|
|
||||||
install -m 644 LICENSE %{buildroot}%{_licensedir}/$PKGNAME/
|
|
||||||
|
|
||||||
if [ -f $PKGNAME.1 ]; then
|
|
||||||
install -m 644 $PKGNAME.1 %{buildroot}%{_mandir}/man1/$PKGNAME.1
|
|
||||||
gzip -9n %{buildroot}%{_mandir}/man1/$PKGNAME.1
|
|
||||||
ln -sf $PKGNAME.1.gz %{buildroot}%{_mandir}/man1/$LEGACY.1.gz
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f completions/vaptvupt.bash ]; then
|
|
||||||
install -m 644 completions/vaptvupt.bash %{buildroot}%{_datadir}/bash-completion/completions/$PKGNAME
|
|
||||||
ln -sf $PKGNAME %{buildroot}%{_datadir}/bash-completion/completions/$LEGACY
|
|
||||||
fi
|
|
||||||
if [ -f completions/_vaptvupt ]; then
|
|
||||||
install -m 644 completions/_vaptvupt %{buildroot}%{_datadir}/zsh/site-functions/_$PKGNAME
|
|
||||||
ln -sf _$PKGNAME %{buildroot}%{_datadir}/zsh/site-functions/_$LEGACY
|
|
||||||
fi
|
|
||||||
if [ -f completions/vaptvupt.fish ]; then
|
|
||||||
install -m 644 completions/vaptvupt.fish %{buildroot}%{_datadir}/fish/vendor_completions.d/$PKGNAME.fish
|
|
||||||
fi
|
|
||||||
|
|
||||||
%files
|
|
||||||
%license %{_licensedir}/$PKGNAME/LICENSE
|
|
||||||
%doc %{_docdir}/$PKGNAME/README.md
|
|
||||||
%doc %{_docdir}/$PKGNAME/CHANGELOG.md
|
|
||||||
%doc %{_docdir}/$PKGNAME/SECURITY.md
|
|
||||||
%doc %{_docdir}/$PKGNAME/AUDIT.md
|
|
||||||
%{_bindir}/$PKGNAME
|
|
||||||
%{_bindir}/$LEGACY
|
|
||||||
%dir %{_libdir}/$PKGNAME
|
|
||||||
%{_libdir}/$PKGNAME/libzuptsdk.so
|
|
||||||
%{_libdir}/$PKGNAME/libzuptsdk.so.2
|
|
||||||
%{_libdir}/$PKGNAME/libzuptsdk.so.2.0.0
|
|
||||||
%{_libdir}/$PKGNAME/libpqvaptvupt.so
|
|
||||||
%{_libdir}/$PKGNAME/libpqvaptvupt.so.0
|
|
||||||
%{_libdir}/$PKGNAME/libpqvaptvupt.so.0.6.0
|
|
||||||
%{_mandir}/man1/$PKGNAME.1.gz
|
|
||||||
%{_mandir}/man1/$LEGACY.1.gz
|
|
||||||
%{_datadir}/bash-completion/completions/$PKGNAME
|
|
||||||
%{_datadir}/bash-completion/completions/$LEGACY
|
|
||||||
%{_datadir}/zsh/site-functions/_$PKGNAME
|
|
||||||
%{_datadir}/zsh/site-functions/_$LEGACY
|
|
||||||
%{_datadir}/fish/vendor_completions.d/$PKGNAME.fish
|
|
||||||
|
|
||||||
%changelog
|
|
||||||
* Sun May 25 2026 Cristian Cezar Moises <zupt@riseup.net> - $VERSION-$RELEASE
|
|
||||||
- v3.0.0: Renamed from "Zupt" to "VaptVupt" because of a prior INPI
|
|
||||||
Brasil trademark on "Zupt". Archive extension .zupt is preserved;
|
|
||||||
v2.x and v3.0.0 archives are bidirectionally compatible. Legacy
|
|
||||||
/usr/bin/zupt is installed as a symlink to /usr/bin/vaptvupt.
|
|
||||||
- Integrated VaptVupt LZ + ANS codec 2.48.5: fixes csz==0 heap-
|
|
||||||
buffer-overflow READ in vv_dstream_decompress_chunk (libFuzzer-
|
|
||||||
found, medium severity), UBSan-safe pointer arithmetic in
|
|
||||||
vv_copy_match.
|
|
||||||
- Enhanced manpage (597 lines, was 422): POST-QUANTUM ENCRYPTION,
|
|
||||||
PERFORMANCE table, SECURITY/threat-model, ENVIRONMENT and
|
|
||||||
EXIT STATUS sections.
|
|
||||||
- Fixed GUI binary-discovery bug (PATH-missing-/usr/bin scenario);
|
|
||||||
GUI now does liveness check + logs discovery to stderr with
|
|
||||||
VAPTVUPT_DEBUG=1.
|
|
||||||
- 91/91 distro-safe regression suite green; F-09 byte sweep
|
|
||||||
0/1827 silent accepts; F-06 HMAC fuzz 0/2000 silent accepts.
|
|
||||||
EOF
|
EOF
|
||||||
|
rpmbuild --define "_topdir $top" -ba "$top/SPECS/zupt.spec"
|
||||||
|
|
||||||
rpmbuild --define "_topdir $RPMROOT" \
|
mapfile -t main_rpms < <(find "$top/RPMS" -type f -name "zupt-${version}-*.rpm" \
|
||||||
--define "_binary_payload w2.gzdio" \
|
! -name '*-debuginfo-*' ! -name '*-debugsource-*' -print | sort)
|
||||||
-bb "$RPMROOT/SPECS/$PKGNAME.spec" 2>&1 | tail -5
|
[[ ${#main_rpms[@]} -eq 1 ]] || die "expected one main RPM, found ${#main_rpms[@]}"
|
||||||
|
main_rpm=${main_rpms[0]}
|
||||||
|
|
||||||
RPM_PATH=$(find "$RPMROOT/RPMS" -name "$PKGNAME-${VERSION}-*.rpm" | head -1)
|
mapfile -t source_rpms < <(find "$top/SRPMS" -type f -name "zupt-${version}-*.src.rpm" -print | sort)
|
||||||
if [ -n "$RPM_PATH" ]; then
|
[[ ${#source_rpms[@]} -eq 1 ]] || die "expected one source RPM, found ${#source_rpms[@]}"
|
||||||
cp "$RPM_PATH" "/tmp/$PKGNAME-${VERSION}-${RELEASE}.${ARCH}.rpm"
|
source_rpm=${source_rpms[0]}
|
||||||
echo ""
|
|
||||||
echo "Built: /tmp/$PKGNAME-${VERSION}-${RELEASE}.${ARCH}.rpm ($(du -h "/tmp/$PKGNAME-${VERSION}-${RELEASE}.${ARCH}.rpm" | cut -f1))"
|
rpm -qpi "$main_rpm" >/dev/null
|
||||||
rpm -qpi "/tmp/$PKGNAME-${VERSION}-${RELEASE}.${ARCH}.rpm" 2>&1 | head -15
|
rpm -qpl "$main_rpm" > "$work/contents.txt"
|
||||||
|
if grep -Eq '(^/usr/bin/vaptvupt$|\.(o|obj|a|so|so\.[^/]+|dll|dylib)$)' "$work/contents.txt"; then
|
||||||
|
cat "$work/contents.txt" >&2
|
||||||
|
die 'forbidden alias or compiled library/object found in RPM contents'
|
||||||
fi
|
fi
|
||||||
|
if grep -q '^/usr/local/' "$work/contents.txt"; then
|
||||||
|
cat "$work/contents.txt" >&2
|
||||||
|
die 'RPM contains files below /usr/local'
|
||||||
|
fi
|
||||||
|
|
||||||
|
(cd -- "$extract" && rpm2cpio "$main_rpm" | cpio -idm --quiet)
|
||||||
|
binary=$extract/usr/bin/zupt
|
||||||
|
[[ -x $binary ]] || die 'RPM does not contain executable /usr/bin/zupt'
|
||||||
|
if ! readelf -h "$binary" 2>/dev/null | grep -Eq 'Type:[[:space:]]+DYN'; then
|
||||||
|
die 'RPM executable is not a position-independent executable (PIE)'
|
||||||
|
fi
|
||||||
|
if ! readelf -W -l "$binary" 2>/dev/null | grep -q 'GNU_RELRO'; then
|
||||||
|
die 'RPM executable lacks a GNU_RELRO segment'
|
||||||
|
fi
|
||||||
|
stack_segment=$(readelf -W -l "$binary" 2>/dev/null | grep 'GNU_STACK' || true)
|
||||||
|
[[ -n $stack_segment && $stack_segment != *RWE* ]] || \
|
||||||
|
die 'RPM executable has a missing or executable GNU_STACK segment'
|
||||||
|
if readelf -d "$binary" 2>/dev/null | grep -Eq '(RPATH|RUNPATH)'; then
|
||||||
|
readelf -d "$binary" | grep -E '(RPATH|RUNPATH)' >&2
|
||||||
|
die 'RPM executable contains RPATH/RUNPATH'
|
||||||
|
fi
|
||||||
|
if readelf -d "$binary" 2>/dev/null | grep -Eqi '(vendor/|libvuptsdk|libpqvaptvupt)'; then
|
||||||
|
die 'RPM executable references a vendored optional library'
|
||||||
|
fi
|
||||||
|
bash scripts/test-installed-zupt.sh "$binary"
|
||||||
|
|
||||||
|
artifacts=("$main_rpm" "$source_rpm")
|
||||||
|
for artifact in "${artifacts[@]}"; do
|
||||||
|
destination=$dist_dir/$(basename -- "$artifact")
|
||||||
|
[[ ! -e $destination ]] || die "refusing to overwrite existing output: $destination"
|
||||||
|
done
|
||||||
|
for artifact in "${artifacts[@]}"; do
|
||||||
|
destination=$dist_dir/$(basename -- "$artifact")
|
||||||
|
cp -- "$artifact" "$destination"
|
||||||
|
sha256sum "$destination"
|
||||||
|
done
|
||||||
|
|
||||||
|
printf 'PASS: built and extracted-package-tested %s\n' "$dist_dir/$(basename -- "$main_rpm")"
|
||||||
|
printf 'PASS: built source RPM %s\n' "$dist_dir/$(basename -- "$source_rpm")"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,30 @@
|
||||||
|
zupt (5.2.2-1) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* Prepare a source-only upstream release and remove incomplete vendored SDK
|
||||||
|
and PQBOX inputs together with every precompiled-library fallback.
|
||||||
|
* Make optional integrations explicit system dependencies, disabled by
|
||||||
|
default, and preserve distribution compiler/linker flags and DESTDIR.
|
||||||
|
* Add the reusable source scanner and openSUSE/OBS source packaging.
|
||||||
|
* Restore the ZUPT/zupt application, package, executable, documentation, and
|
||||||
|
artifact names; build and test with WITH_SDK=0 WITH_PQBOX=0.
|
||||||
|
* Add explicit password prompt, file, and inherited-descriptor inputs.
|
||||||
|
* Correct the licensing record without revoking historical MIT grants present
|
||||||
|
in earlier repository revisions; current files follow current SPDX notices.
|
||||||
|
* Preserve Yann Collet's BSD-2-Clause notice for the two xxHash-derived
|
||||||
|
XXH64 source units and include it in package license metadata.
|
||||||
|
* Record the CC0-1.0 option for pq-crystals/kyber-derived ML-KEM portions
|
||||||
|
and ship the complete license text in every binary bundle.
|
||||||
|
* Preserve the BSD-3-Clause notice for curve25519-donna-derived X25519
|
||||||
|
portions and document their provenance without inventing a revision.
|
||||||
|
* Promote only license-complete release assets: Windows is ZIP-only and the
|
||||||
|
AppImage remains downstream-only pending a complete runtime source/relink
|
||||||
|
compliance handoff.
|
||||||
|
* Qualify older changelog statements about formally verified or
|
||||||
|
constant-time assembly: 5.2.2 retains source, generated output and runtime
|
||||||
|
regressions, but no reproducible formal-proof certificate for those paths.
|
||||||
|
|
||||||
|
-- Cristian Cezar Moisés <sac@securityops.co> Mon, 31 Aug 2026 00:00:00 +0000
|
||||||
|
|
||||||
vaptvupt (5.0.0-1) UNRELEASED; urgency=high
|
vaptvupt (5.0.0-1) UNRELEASED; urgency=high
|
||||||
|
|
||||||
* ML-KEM-768 is now genuinely FIPS 203-conformant. Earlier releases shipped
|
* ML-KEM-768 is now genuinely FIPS 203-conformant. Earlier releases shipped
|
||||||
|
|
@ -377,12 +404,10 @@ vaptvupt (3.0.2-1) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
vaptvupt (3.0.1-1) UNRELEASED; urgency=medium
|
vaptvupt (3.0.1-1) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
* GUI license cleanup: removed MIT-license credit line from the
|
* GUI license metadata changed to AGPL-3.0-or-later for the then-current
|
||||||
about panel (the GUI is AGPL-3.0-or-later with commercial dual-
|
source. The original entry incorrectly called earlier MIT notices a
|
||||||
licensing; the MIT reference was a templating mistake). Replaced
|
templating mistake; the 5.2.2 erratum records that historical grants remain
|
||||||
gui/LICENSE-GUI (was MIT) with AGPL-3.0-or-later, mirroring the
|
valid for the exact material distributed under them.
|
||||||
top-level LICENSE. Top-level LICENSE preamble updated to reflect
|
|
||||||
the v3.0.0 Zupt → VaptVupt rename.
|
|
||||||
* GUI version-string parsing bug fix: the v3.0.0 GUI used
|
* GUI version-string parsing bug fix: the v3.0.0 GUI used
|
||||||
`replace("zupt ", "")` to peel the product name out of the CLI's
|
`replace("zupt ", "")` to peel the product name out of the CLI's
|
||||||
version banner, but that substring also appears inside the v3.0.0
|
version banner, but that substring also appears inside the v3.0.0
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,49 @@
|
||||||
Source: vaptvupt
|
Source: zupt
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
|
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
|
||||||
Build-Depends:
|
Build-Depends:
|
||||||
|
bash,
|
||||||
|
coreutils,
|
||||||
debhelper-compat (= 13),
|
debhelper-compat (= 13),
|
||||||
|
diffutils,
|
||||||
|
file,
|
||||||
|
findutils,
|
||||||
gcc,
|
gcc,
|
||||||
|
gawk,
|
||||||
|
git,
|
||||||
|
grep,
|
||||||
|
gzip,
|
||||||
|
libarchive-tools,
|
||||||
|
make,
|
||||||
libc6-dev,
|
libc6-dev,
|
||||||
python3 (>= 3.8)
|
python3 (>= 3.8),
|
||||||
|
sed,
|
||||||
|
tar
|
||||||
Standards-Version: 4.6.2
|
Standards-Version: 4.6.2
|
||||||
Homepage: https://git.securityops.co/cristiancmoises/vaptvupt
|
Homepage: https://github.com/cristiancmoises/zupt
|
||||||
Vcs-Browser: https://git.securityops.co/cristiancmoises/vaptvupt
|
Vcs-Browser: https://github.com/cristiancmoises/zupt
|
||||||
Vcs-Git: https://git.securityops.co/cristiancmoises/vaptvupt.git
|
Vcs-Git: https://github.com/cristiancmoises/zupt.git
|
||||||
Rules-Requires-Root: no
|
Rules-Requires-Root: no
|
||||||
|
|
||||||
Package: vaptvupt
|
Package: zupt
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Provides: zupt (= ${binary:Version})
|
|
||||||
Replaces: zupt
|
|
||||||
Conflicts: zupt
|
|
||||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||||
Description: Post-quantum backup compression utility (formerly Zupt)
|
Description: Post-quantum backup compression utility
|
||||||
VaptVupt (renamed from Zupt in v3.0.0 due to INPI Brasil trademark) is
|
ZUPT is a pure-C11 backup compression utility featuring:
|
||||||
a pure-C11 backup compression utility featuring:
|
|
||||||
* Post-quantum hybrid encryption (ML-KEM-768 + X25519, FIPS 203)
|
* Post-quantum hybrid encryption (ML-KEM-768 + X25519, FIPS 203)
|
||||||
* AES-256-CTR + HMAC-SHA256 authenticated encryption (Encrypt-then-MAC)
|
* AES-256-CTR + HMAC-SHA256 authenticated encryption (Encrypt-then-MAC)
|
||||||
* PBKDF2-SHA256 password key derivation (Argon2id in WITH_SDK=1 builds)
|
* PBKDF2-SHA256 password key derivation (Argon2id in WITH_SDK=1 builds)
|
||||||
* Multi-threaded compression with the VaptVupt LZ + ANS codec 2.60.4
|
* Multi-threaded compression with the VaptVupt LZ + ANS codec 2.65.3
|
||||||
* Full-disk backup and restore with sparse-region detection
|
* Full-disk backup and restore with sparse-region detection
|
||||||
* End-to-end byte-level tamper detection on encrypted archives
|
* Authenticated encrypted-archive metadata and per-block integrity checks
|
||||||
(0 silent-accept positions in the v1.6 exhaustive byte sweep)
|
* Portable C implementations with optional source-built assembly paths
|
||||||
* Constant-time cryptographic primitives verified with Jasmin
|
|
||||||
* NIST/RFC test vectors for SHA-256, SHA-3, ML-KEM-768, AES-256-CTR,
|
* NIST/RFC test vectors for SHA-256, SHA-3, ML-KEM-768, AES-256-CTR,
|
||||||
HMAC-SHA256, X25519, PBKDF2, Argon2id
|
HMAC-SHA256, X25519 and PBKDF2
|
||||||
.
|
.
|
||||||
The archive extension stays .zupt for format continuity (header magic
|
The archive extension stays .zupt for format continuity (header magic
|
||||||
unchanged). The binary `zupt` is preserved as a symlink to `vaptvupt`.
|
unchanged). The package installs only /usr/bin/zupt.
|
||||||
.
|
.
|
||||||
The archive format includes an integrity trailer that authenticates the
|
Encrypted archives include an integrity trailer that authenticates the
|
||||||
header and footer, per-block HMAC with bound frame-preface AAD, and
|
header and footer, per-block HMAC with bound frame-preface AAD, and optional
|
||||||
optional encrypted comments.
|
encrypted comments. Plain archives use non-cryptographic checksums.
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,35 @@
|
||||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
Upstream-Name: zupt
|
Upstream-Name: ZUPT
|
||||||
Upstream-Contact: Cristian Cezar Moisés <sac@securityops.co>
|
Upstream-Contact: Cristian Cezar Moisés <sac@securityops.co>
|
||||||
Source: https://git.securityops.co/cristiancmoises/vaptvupt
|
Source: https://github.com/cristiancmoises/zupt
|
||||||
|
|
||||||
Files: *
|
Files: *
|
||||||
Copyright: 2025-2026 Cristian Cezar Moisés
|
Copyright: 2025-2026 Cristian Cezar Moisés
|
||||||
License: AGPL-3.0-or-later
|
License: AGPL-3.0-or-later
|
||||||
|
|
||||||
Files: src/vv_*.c include/vaptvupt*.h include/vv_*.h vendor/zuptsdk/include/vv_*.h vendor/zuptsdk/include/vaptvupt*.h
|
Files: src/vaptvupt_api.c src/vv_*.c include/vaptvupt*.h include/vv_*.h
|
||||||
Copyright: 2025-2026 Cristian Cezar Moisés (VaptVupt codec)
|
Copyright: 2025-2026 Cristian Cezar Moisés (VaptVupt codec)
|
||||||
License: GPL-3.0-or-later
|
License: GPL-3.0-or-later
|
||||||
|
|
||||||
Files: vendor/zuptsdk/*
|
Files: src/zupt_xxh.c
|
||||||
Copyright: 2025-2026 Cristian Cezar Moisés (libzuptsdk)
|
Copyright: 2012-2021 Yann Collet
|
||||||
License: GPL-3.0-or-later
|
2025-2026 Cristian Cezar Moisés
|
||||||
|
License: AGPL-3.0-or-later and BSD-2-Clause
|
||||||
|
|
||||||
|
Files: src/zupt_mlkem.c
|
||||||
|
Copyright: 2025-2026 Cristian Cezar Moisés
|
||||||
|
pq-crystals/kyber contributors (adapted portions)
|
||||||
|
License: AGPL-3.0-or-later and CC0-1.0
|
||||||
|
|
||||||
|
Files: src/zupt_x25519.c
|
||||||
|
Copyright: 2008 Google Inc.
|
||||||
|
2025-2026 Cristian Cezar Moisés
|
||||||
|
License: AGPL-3.0-or-later and BSD-3-Clause
|
||||||
|
|
||||||
|
Files: src/vv_xxh64.c
|
||||||
|
Copyright: 2012-2021 Yann Collet
|
||||||
|
2025-2026 Cristian Cezar Moisés (VaptVupt codec adaptation)
|
||||||
|
License: GPL-3.0-or-later and BSD-2-Clause
|
||||||
|
|
||||||
Files: debian/*
|
Files: debian/*
|
||||||
Copyright: 2025-2026 Cristian Cezar Moisés <sac@securityops.co>
|
Copyright: 2025-2026 Cristian Cezar Moisés <sac@securityops.co>
|
||||||
|
|
@ -47,3 +63,41 @@ License: GPL-3.0-or-later
|
||||||
.
|
.
|
||||||
On Debian systems, the full text of the GNU General Public License
|
On Debian systems, the full text of the GNU General Public License
|
||||||
version 3 can be found in the file `/usr/share/common-licenses/GPL-3'.
|
version 3 can be found in the file `/usr/share/common-licenses/GPL-3'.
|
||||||
|
|
||||||
|
License: BSD-2-Clause
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
.
|
||||||
|
* Redistributions of source code must retain the copyright notice, this list
|
||||||
|
of conditions and the disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the copyright notice, this
|
||||||
|
list of conditions and the disclaimer in the documentation and/or other
|
||||||
|
materials provided with the distribution.
|
||||||
|
.
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
License: CC0-1.0
|
||||||
|
To the extent possible under law, the upstream affirmer has waived all
|
||||||
|
copyright and related or neighboring rights to the adapted portions.
|
||||||
|
.
|
||||||
|
The complete CC0 1.0 Universal legal text is shipped in
|
||||||
|
`/usr/share/doc/zupt/LICENSE-CC0-1.0`.
|
||||||
|
|
||||||
|
License: BSD-3-Clause
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the copyright notice, conditions,
|
||||||
|
and disclaimer are retained; neither the name of Google Inc. nor contributor
|
||||||
|
names may be used to endorse derived products without prior permission.
|
||||||
|
.
|
||||||
|
The complete BSD-3-Clause notice and disclaimer are shipped in
|
||||||
|
`/usr/share/doc/zupt/LICENSE-BSD-3-Clause`.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
# Honour Debian's reproducible-build epoch when set by dpkg-buildpackage.
|
# Honour Debian's reproducible-build epoch when set by dpkg-buildpackage.
|
||||||
export SOURCE_DATE_EPOCH ?= 1747699200
|
export SOURCE_DATE_EPOCH ?= 1788134400
|
||||||
|
|
||||||
# Hardening flags — Debian's defaults are already strong, this adds project-
|
# Hardening flags — Debian's defaults are already strong, this adds project-
|
||||||
# specific ones.
|
# specific ones.
|
||||||
|
|
@ -15,21 +15,18 @@ export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
|
||||||
|
|
||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
# Source-only build: no vendored libraries, native crypto only.
|
# Source-only build: no vendored libraries, native crypto only.
|
||||||
$(MAKE) WITH_SDK=0 -j$$(nproc)
|
$(MAKE) WITH_SDK=0 WITH_PQBOX=0 -j$$(nproc)
|
||||||
|
|
||||||
override_dh_auto_test:
|
override_dh_auto_test:
|
||||||
# Distro-safe regression subset (F-06..F-12, dedup-nonce, NIST/RFC vectors).
|
# Distro-safe quick, traversal, integrity, codec and NIST/RFC checks.
|
||||||
$(MAKE) WITH_SDK=0 check
|
$(MAKE) WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
|
|
||||||
override_dh_auto_install:
|
override_dh_auto_install:
|
||||||
# Binary package is `vaptvupt` -> stage into debian/vaptvupt (dh derives the
|
# Binary package is `zupt` -> stage into debian/zupt (dh derives the
|
||||||
# staging dir from the Package: name in debian/control). Source-only: nothing
|
# staging dir from the Package: name in debian/control). Source-only: nothing
|
||||||
# to install beyond `make install` (no .so).
|
# to install beyond `make install` (no .so).
|
||||||
$(MAKE) DESTDIR=$(CURDIR)/debian/vaptvupt PREFIX=/usr WITH_SDK=0 install
|
$(MAKE) DESTDIR=$(CURDIR)/debian/zupt PREFIX=/usr \
|
||||||
|
WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 install
|
||||||
|
|
||||||
override_dh_auto_clean:
|
override_dh_auto_clean:
|
||||||
$(MAKE) clean
|
$(MAKE) clean
|
||||||
|
|
||||||
# Skip dh_strip's separate -dbgsym packages for a single-source-package layout.
|
|
||||||
override_dh_strip:
|
|
||||||
dh_strip --no-automatic-dbgsym
|
|
||||||
|
|
|
||||||
12
packaging/debian/zupt.docs
Normal file
12
packaging/debian/zupt.docs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
README.md
|
||||||
|
CHANGELOG.md
|
||||||
|
SECURITY.md
|
||||||
|
THREAT_MODEL.md
|
||||||
|
NOTICE
|
||||||
|
THIRD-PARTY-NOTICES.md
|
||||||
|
LICENSE
|
||||||
|
LICENSE-AGPL-3.0
|
||||||
|
LICENSE-GPL-3.0
|
||||||
|
LICENSE-BSD-2-Clause
|
||||||
|
LICENSE-BSD-3-Clause
|
||||||
|
LICENSE-CC0-1.0
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
;;; SPDX-License-Identifier: AGPL-3.0-or-later
|
;;; SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
;;; Copyright (c) 2026 Cristian Cezar Moisés
|
;;; Copyright (c) 2026 Cristian Cezar Moisés
|
||||||
;;;
|
;;;
|
||||||
;;; GNU Guix package definitions for VaptVupt (CLI + PySide6 GUI).
|
;;; GNU Guix package definitions for ZUPT (CLI + PySide6 GUI).
|
||||||
;;; Source-only build (no vendored libraries): the CLI links only libc/libm/
|
;;; Source-only build (no vendored libraries): the CLI links only libc/libm/
|
||||||
;;; pthread from the store.
|
;;; pthread from the store.
|
||||||
;;;
|
;;;
|
||||||
;;; Install into your profile (additive; keeps everything else):
|
;;; Install into your profile (additive; keeps everything else):
|
||||||
;;; guix package -f packaging/guix/vaptvupt.scm ; installs the GUI
|
;;; guix package -f packaging/guix/zupt.scm ; installs the GUI
|
||||||
;;; guix package -e '(@ (guix) …)' — or, for the CLI on its own:
|
;;; guix package -e '(@ (guix) …)' — or, for the CLI on its own:
|
||||||
;;; guix install -f packaging/guix/vaptvupt.scm ; (last expr = GUI)
|
;;; guix install -f packaging/guix/zupt.scm ; (last expr = GUI)
|
||||||
;;; The last expression is the GUI, which carries the CLI as an input; to get
|
;;; The last expression is the GUI, which carries the CLI as an input; to get
|
||||||
;;; the `vaptvupt` command in your profile too, also run:
|
;;; the `zupt` command in your profile too, also run:
|
||||||
;;; guix package --install-from-expression='(begin (load "packaging/guix/vaptvupt.scm") vaptvupt)'
|
;;; guix package --install-from-expression='(begin (load "packaging/guix/zupt.scm") zupt)'
|
||||||
;;;
|
;;;
|
||||||
;;; GUI-on-Guix note: PySide6's Qt6 links several leaf libraries (libGL from
|
;;; GUI-on-Guix note: PySide6's Qt6 links several leaf libraries (libGL from
|
||||||
;;; mesa, libxkbcommon, the X11/xcb family, libzstd, harfbuzz, icu, ...) that are
|
;;; mesa, libxkbcommon, the X11/xcb family, libzstd, harfbuzz, icu, ...) that are
|
||||||
|
|
@ -62,29 +62,30 @@
|
||||||
xcb-util-renderutil xcb-util-wm xcb-util-cursor
|
xcb-util-renderutil xcb-util-wm xcb-util-cursor
|
||||||
libinput-minimal mtdev libevdev eudev))
|
libinput-minimal mtdev libevdev eudev))
|
||||||
|
|
||||||
(define %vaptvupt-version "5.2.1")
|
(define %zupt-version "5.2.2")
|
||||||
|
|
||||||
(define %vaptvupt-source
|
(define %zupt-source
|
||||||
(origin
|
(origin
|
||||||
(method url-fetch)
|
(method url-fetch)
|
||||||
(uri (string-append
|
(uri (string-append
|
||||||
"https://git.securityops.co/cristiancmoises/vaptvupt"
|
"https://github.com/cristiancmoises/zupt"
|
||||||
"/releases/download/v" %vaptvupt-version
|
"/releases/download/v" %zupt-version
|
||||||
"/vaptvupt-" %vaptvupt-version ".tar.gz"))
|
"/zupt-" %zupt-version ".tar.gz"))
|
||||||
(sha256
|
(sha256
|
||||||
(base32 "1mzl5za5k80x74p1hb9kfi199fs74ymmlcdhhxkzxr9ls8gpg6z2"))))
|
(base32 "REPLACE_AFTER_FINAL_RELEASE_ARCHIVE_IS_BUILT"))))
|
||||||
|
|
||||||
(define-public vaptvupt
|
(define-public zupt
|
||||||
(package
|
(package
|
||||||
(name "vaptvupt")
|
(name "zupt")
|
||||||
(version %vaptvupt-version)
|
(version %zupt-version)
|
||||||
(source %vaptvupt-source)
|
(source %zupt-source)
|
||||||
(build-system gnu-build-system)
|
(build-system gnu-build-system)
|
||||||
(arguments
|
(arguments
|
||||||
(list
|
(list
|
||||||
#:make-flags
|
#:make-flags
|
||||||
#~(list (string-append "PREFIX=" #$output)
|
#~(list (string-append "PREFIX=" #$output)
|
||||||
"WITH_SDK=0"
|
"WITH_SDK=0"
|
||||||
|
"WITH_PQBOX=0"
|
||||||
(string-append "CC=" #$(cc-for-target)))
|
(string-append "CC=" #$(cc-for-target)))
|
||||||
#:phases
|
#:phases
|
||||||
#~(modify-phases %standard-phases
|
#~(modify-phases %standard-phases
|
||||||
|
|
@ -94,38 +95,49 @@
|
||||||
;; SP 800-38A, RFC 4231/7748) are the crypto gate.
|
;; SP 800-38A, RFC 4231/7748) are the crypto gate.
|
||||||
(lambda* (#:key tests? #:allow-other-keys)
|
(lambda* (#:key tests? #:allow-other-keys)
|
||||||
(when tests?
|
(when tests?
|
||||||
(invoke "make" "WITH_SDK=0"
|
(invoke "make" "WITH_SDK=0" "WITH_PQBOX=0"
|
||||||
(string-append "CC=" #$(cc-for-target))
|
(string-append "CC=" #$(cc-for-target))
|
||||||
"test-vectors")
|
"test-vectors")
|
||||||
(invoke "./test_vectors")))))))
|
(invoke "./test_vectors")))))))
|
||||||
(home-page "https://git.securityops.co/cristiancmoises/vaptvupt")
|
(home-page "https://github.com/cristiancmoises/zupt")
|
||||||
(synopsis "Post-quantum backup compression utility")
|
(synopsis "Post-quantum backup compression utility")
|
||||||
(description
|
(description
|
||||||
"VaptVupt (formerly Zupt) is a pure-C11 backup compressor with native
|
"ZUPT is a pure-C11 backup compressor with native
|
||||||
post-quantum encryption. Two in-tree PQ modes: @code{--pq} hybridizes
|
post-quantum encryption. Two in-tree PQ modes: @code{--pq} hybridizes
|
||||||
ML-KEM-768 (FIPS 203, validated against OpenSSL) with X25519 (recommended), and
|
ML-KEM-768 with X25519 (recommended), and
|
||||||
@code{--pq-only} uses ML-KEM-768 alone for @dfn{PQ-only} compliance postures.
|
@code{--pq-only} uses ML-KEM-768 alone for @dfn{PQ-only} compliance postures.
|
||||||
Payload protection is AES-256-CTR + HMAC-SHA256 Encrypt-then-MAC with a fresh
|
Payload protection is AES-256-CTR + HMAC-SHA256 Encrypt-then-MAC with a fresh
|
||||||
random per-block nonce and measured constant-time tag comparison; AES-NI/SHA-NI
|
random per-block nonce; AES-NI/SHA-NI dispatch at runtime; the bundled
|
||||||
dispatch at runtime; the embedded VaptVupt 2.60.4 LZ+ANS codec ships
|
VaptVupt 2.65.3 LZ+ANS codec has portable fallbacks. Password mode uses
|
||||||
CBMC-verified BCJ filters. Password mode uses PBKDF2-SHA256. The tool is
|
PBKDF2-SHA256. The tool is
|
||||||
AGPL-3.0-or-later; the embedded codec is GPL-3.0-or-later.")
|
AGPL-3.0-or-later; the embedded codec is GPL-3.0-or-later; the two
|
||||||
(license (list license:agpl3+ license:gpl3+))))
|
xxHash-derived XXH64 units additionally carry BSD-2-Clause; and portions of
|
||||||
|
native ML-KEM adapted from pq-crystals/kyber carry CC0-1.0. Native X25519
|
||||||
|
portions adapted from curve25519-donna retain BSD-3-Clause.
|
||||||
|
The x86 BCJ filter and SHA-NI path also record their public-domain LZMA SDK
|
||||||
|
and SHA-Intrinsics origins; installed NOTICE and THIRD-PARTY-NOTICES.md carry
|
||||||
|
the full provenance record.")
|
||||||
|
(license (list license:agpl3+ license:gpl3+ license:bsd-2 license:bsd-3 license:cc0))))
|
||||||
|
|
||||||
(define-public vaptvupt-gui
|
(define-public zupt-gui
|
||||||
(package
|
(package
|
||||||
(name "vaptvupt-gui")
|
(name "zupt-gui")
|
||||||
(version %vaptvupt-version)
|
(version %zupt-version)
|
||||||
(source (package-source vaptvupt)) ; same release tarball
|
(source (package-source zupt)) ; same release tarball
|
||||||
(build-system copy-build-system)
|
(build-system copy-build-system)
|
||||||
(arguments
|
(arguments
|
||||||
(list
|
(list
|
||||||
#:install-plan
|
#:install-plan
|
||||||
#~'(("gui/src/zupt_gui.py" "lib/vaptvupt-gui/")
|
#~'(("gui/src/zupt_gui.py" "lib/zupt-gui/")
|
||||||
("gui/assets/zupt-icon.png"
|
("gui/assets/zupt-icon.png"
|
||||||
"share/icons/hicolor/256x256/apps/vaptvupt-gui.png")
|
"share/icons/hicolor/256x256/apps/zupt-gui.png")
|
||||||
("gui/README.md" "share/doc/vaptvupt-gui/")
|
("gui/README.md" "share/doc/zupt-gui/")
|
||||||
("gui/LICENSE-GUI" "share/doc/vaptvupt-gui/"))
|
("LICENSE-AGPL-3.0"
|
||||||
|
"share/licenses/zupt-gui/LICENSE-AGPL-3.0")
|
||||||
|
("gui/LICENSE-GUI"
|
||||||
|
"share/licenses/zupt-gui/LICENSE-GUI")
|
||||||
|
("gui/assets/README.md"
|
||||||
|
"share/licenses/zupt-gui/ASSET-PROVENANCE.md"))
|
||||||
#:phases
|
#:phases
|
||||||
#~(modify-phases %standard-phases
|
#~(modify-phases %standard-phases
|
||||||
(add-after 'install 'make-launcher
|
(add-after 'install 'make-launcher
|
||||||
|
|
@ -133,10 +145,10 @@ AGPL-3.0-or-later; the embedded codec is GPL-3.0-or-later.")
|
||||||
(let* ((out (assoc-ref outputs "out"))
|
(let* ((out (assoc-ref outputs "out"))
|
||||||
(bin (string-append out "/bin"))
|
(bin (string-append out "/bin"))
|
||||||
(gui (string-append
|
(gui (string-append
|
||||||
out "/lib/vaptvupt-gui/zupt_gui.py"))
|
out "/lib/zupt-gui/zupt_gui.py"))
|
||||||
(sh (search-input-file inputs "/bin/sh"))
|
(sh (search-input-file inputs "/bin/sh"))
|
||||||
(python3 (search-input-file inputs "/bin/python3"))
|
(python3 (search-input-file inputs "/bin/python3"))
|
||||||
(cli (search-input-file inputs "/bin/vaptvupt"))
|
(cli (search-input-file inputs "/bin/zupt"))
|
||||||
(pyside (assoc-ref inputs "python-pyside-6"))
|
(pyside (assoc-ref inputs "python-pyside-6"))
|
||||||
(site (car (find-files pyside "^site-packages$"
|
(site (car (find-files pyside "^site-packages$"
|
||||||
#:directories? #t)))
|
#:directories? #t)))
|
||||||
|
|
@ -156,32 +168,31 @@ AGPL-3.0-or-later; the embedded codec is GPL-3.0-or-later.")
|
||||||
(list (string-append zstdlib "/lib")))
|
(list (string-append zstdlib "/lib")))
|
||||||
":")))
|
":")))
|
||||||
(mkdir-p bin)
|
(mkdir-p bin)
|
||||||
(call-with-output-file (string-append bin "/vaptvupt-gui")
|
(call-with-output-file (string-append bin "/zupt-gui")
|
||||||
(lambda (port)
|
(lambda (port)
|
||||||
(format port "#!~a
|
(format port "#!~a
|
||||||
export VAPTVUPT_BIN=\"~a\"
|
export ZUPT_BIN=\"~a\"
|
||||||
export GUIX_PYTHONPATH=\"~a:~a${GUIX_PYTHONPATH:+:}$GUIX_PYTHONPATH\"
|
export GUIX_PYTHONPATH=\"~a:~a${GUIX_PYTHONPATH:+:}$GUIX_PYTHONPATH\"
|
||||||
export QT_PLUGIN_PATH=\"~a/lib/qt6/plugins:~a/lib/qt6/plugins${QT_PLUGIN_PATH:+:}$QT_PLUGIN_PATH\"
|
export QT_PLUGIN_PATH=\"~a/lib/qt6/plugins:~a/lib/qt6/plugins${QT_PLUGIN_PATH:+:}$QT_PLUGIN_PATH\"
|
||||||
export LD_LIBRARY_PATH=\"~a${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH\"
|
export LD_LIBRARY_PATH=\"~a${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH\"
|
||||||
exec \"~a\" \"~a\" \"$@\"\n"
|
exec \"~a\" \"~a\" \"$@\"\n"
|
||||||
sh cli site shsite qtbase qtwl ldpath python3 gui)))
|
sh cli site shsite qtbase qtwl ldpath python3 gui)))
|
||||||
(chmod (string-append bin "/vaptvupt-gui") #o755)
|
(chmod (string-append bin "/zupt-gui") #o755))))
|
||||||
(symlink "vaptvupt-gui" (string-append bin "/zupt-gui")))))
|
|
||||||
(add-after 'make-launcher 'install-desktop-file
|
(add-after 'make-launcher 'install-desktop-file
|
||||||
(lambda* (#:key outputs #:allow-other-keys)
|
(lambda* (#:key outputs #:allow-other-keys)
|
||||||
(let* ((out (assoc-ref outputs "out"))
|
(let* ((out (assoc-ref outputs "out"))
|
||||||
(apps (string-append out "/share/applications")))
|
(apps (string-append out "/share/applications")))
|
||||||
(mkdir-p apps)
|
(mkdir-p apps)
|
||||||
(call-with-output-file
|
(call-with-output-file
|
||||||
(string-append apps "/vaptvupt-gui.desktop")
|
(string-append apps "/zupt-gui.desktop")
|
||||||
(lambda (port)
|
(lambda (port)
|
||||||
(format port "[Desktop Entry]
|
(format port "[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=VaptVupt
|
Name=ZUPT
|
||||||
GenericName=Post-Quantum Backup
|
GenericName=Post-Quantum Backup
|
||||||
Comment=Compress, encrypt and restore .zupt archives
|
Comment=Compress, encrypt and restore .zupt archives
|
||||||
Exec=~a/bin/vaptvupt-gui %F
|
Exec=~a/bin/zupt-gui %F
|
||||||
Icon=vaptvupt-gui
|
Icon=zupt-gui
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Utility;Archiving;Security;
|
Categories=Utility;Archiving;Security;
|
||||||
MimeType=application/x-zupt;
|
MimeType=application/x-zupt;
|
||||||
|
|
@ -189,20 +200,20 @@ Keywords=backup;encryption;post-quantum;compression;zupt;\n"
|
||||||
out)))))))))
|
out)))))))))
|
||||||
(inputs
|
(inputs
|
||||||
(append (list bash-minimal python python-pyside-6 python-shiboken-6
|
(append (list bash-minimal python python-pyside-6 python-shiboken-6
|
||||||
qtbase qtwayland vaptvupt
|
qtbase qtwayland zupt
|
||||||
(list zstd "lib")) ; libzstd.so.1 is in zstd's "lib" output
|
(list zstd "lib")) ; libzstd.so.1 is in zstd's "lib" output
|
||||||
%gui-runtime-libs))
|
%gui-runtime-libs))
|
||||||
(home-page "https://git.securityops.co/cristiancmoises/vaptvupt")
|
(home-page "https://github.com/cristiancmoises/zupt")
|
||||||
(synopsis "Desktop frontend for the VaptVupt post-quantum backup tool")
|
(synopsis "Desktop frontend for the ZUPT post-quantum backup tool")
|
||||||
(description
|
(description
|
||||||
"PySide6 (Qt 6) graphical frontend for VaptVupt: create, inspect and
|
"PySide6 (Qt 6) graphical frontend for ZUPT: create, inspect and
|
||||||
extract @code{.zupt} archives with password or post-quantum recipient
|
extract @code{.zupt} archives with password or post-quantum recipient
|
||||||
encryption, including the @code{--pq} hybrid and @code{--pq-only} full
|
encryption, including the @code{--pq} hybrid and @code{--pq-only} full
|
||||||
post-quantum modes. The launcher pins the matching @code{vaptvupt} CLI from the
|
post-quantum modes. The launcher pins the matching @code{zupt} CLI from the
|
||||||
store via @env{VAPTVUPT_BIN} and sets @env{LD_LIBRARY_PATH} to the Qt6 leaf
|
store via @env{ZUPT_BIN} and sets @env{LD_LIBRARY_PATH} to the Qt6 leaf
|
||||||
libraries PySide6 needs but does not carry in its RUNPATH.")
|
libraries PySide6 needs but does not carry in its RUNPATH.")
|
||||||
(license license:agpl3+)))
|
(license license:agpl3+)))
|
||||||
|
|
||||||
;; `guix package -f' evaluates the file's last expression — the GUI, which
|
;; `guix package -f' evaluates the file's last expression — the GUI, which
|
||||||
;; carries the CLI as an input.
|
;; carries the CLI as an input.
|
||||||
vaptvupt-gui
|
zupt-gui
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# Homebrew formula for zupt.
|
# Homebrew formula for ZUPT.
|
||||||
#
|
#
|
||||||
# To publish:
|
# To publish:
|
||||||
# 1. Run `make dist` upstream to produce zupt-VERSION.tar.gz (reproducible).
|
# 1. Run `make dist` upstream to produce zupt-VERSION.tar.gz.
|
||||||
# 2. Upload to a stable release URL.
|
# 2. Upload to a stable release URL.
|
||||||
# 3. Update `url`, `version`, and `sha256` below.
|
# 3. Update `url`, `version`, and `sha256` below.
|
||||||
# 4. Submit to homebrew-core via PR OR host in your own tap
|
# 4. Submit to homebrew-core via PR OR host in your own tap
|
||||||
|
|
@ -19,13 +19,13 @@
|
||||||
# the C fallback for AES-256-CTR / HMAC compare paths is shipped.
|
# the C fallback for AES-256-CTR / HMAC compare paths is shipped.
|
||||||
# * Source-only build: no vendored libraries; native crypto only.
|
# * Source-only build: no vendored libraries; native crypto only.
|
||||||
|
|
||||||
class Vaptvupt < Formula
|
class Zupt < Formula
|
||||||
desc "Post-quantum backup compression utility (ML-KEM-768 + AES-256-CTR + HMAC-SHA256)"
|
desc "Post-quantum backup compression utility (ML-KEM-768 + AES-256-CTR + HMAC-SHA256)"
|
||||||
homepage "https://git.securityops.co/cristiancmoises/vaptvupt"
|
homepage "https://github.com/cristiancmoises/zupt"
|
||||||
url "https://git.securityops.co/cristiancmoises/vaptvupt/releases/download/v5.2.1/vaptvupt-5.2.1.tar.gz"
|
url "https://github.com/cristiancmoises/zupt/releases/download/v5.2.2/zupt-5.2.2.tar.gz"
|
||||||
version "5.2.1"
|
version "5.2.2"
|
||||||
sha256 "REPLACE_WITH_SHA256_OF_RELEASE_TARBALL"
|
sha256 "REPLACE_WITH_SHA256_OF_RELEASE_TARBALL"
|
||||||
license "AGPL-3.0-or-later"
|
license all_of: ["AGPL-3.0-or-later", "GPL-3.0-or-later", "BSD-2-Clause", "BSD-3-Clause", "CC0-1.0"]
|
||||||
|
|
||||||
depends_on "python@3.12" => :test # only for test-suite tamper harness
|
depends_on "python@3.12" => :test # only for test-suite tamper harness
|
||||||
|
|
||||||
|
|
@ -35,18 +35,24 @@ class Vaptvupt < Formula
|
||||||
# this and falls back cleanly.
|
# this and falls back cleanly.
|
||||||
ENV["CFLAGS"] = "#{ENV.cflags} -O2 -std=c11 -Wall -Wextra"
|
ENV["CFLAGS"] = "#{ENV.cflags} -O2 -std=c11 -Wall -Wextra"
|
||||||
|
|
||||||
system "make", "WITH_SDK=0", "-j#{ENV.make_jobs}"
|
system "make", "WITH_SDK=0", "WITH_PQBOX=0", "-j#{ENV.make_jobs}"
|
||||||
system "make", "DESTDIR=#{prefix}", "PREFIX=", "WITH_SDK=0", "install"
|
system "make", "PREFIX=#{prefix}", "WITH_SDK=0", "WITH_PQBOX=0",
|
||||||
|
"INSTALL_LEGACY_ALIAS=0", "install"
|
||||||
|
|
||||||
# Docs (no vendored .so/.dylib in the source-only build).
|
# Docs (no vendored .so/.dylib in the source-only build). `make install`
|
||||||
|
# also installs the complete project license/notice set.
|
||||||
doc.install "README.md", "SECURITY.md", "CHANGELOG.md"
|
doc.install "README.md", "SECURITY.md", "CHANGELOG.md"
|
||||||
|
%w[LICENSE-BSD-3-Clause LICENSE-CC0-1.0].each do |notice|
|
||||||
|
odie "missing installed license #{notice}" unless \
|
||||||
|
(share/"licenses/zupt"/notice).exist?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test do
|
test do
|
||||||
# End-to-end sanity check: build a real archive, extract it, byte-compare.
|
# End-to-end sanity check: build a real archive, extract it, byte-compare.
|
||||||
(testpath/"input.txt").write("homebrew formula test payload\n")
|
(testpath/"input.txt").write("homebrew formula test payload\n")
|
||||||
system bin/"zupt", "c", "-p", "test", "out.zupt", "input.txt"
|
system bin/"zupt", "c", "-p", "test", "out.zupt", "input.txt"
|
||||||
system bin/"zupt", "info", "out.zupt"
|
system bin/"zupt", "t", "-p", "test", "out.zupt"
|
||||||
mkdir "extracted"
|
mkdir "extracted"
|
||||||
cd "extracted" do
|
cd "extracted" do
|
||||||
system bin/"zupt", "x", "-p", "test", "../out.zupt"
|
system bin/"zupt", "x", "-p", "test", "../out.zupt"
|
||||||
|
|
@ -1,139 +1,8 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
|
||||||
# Zupt + Zupt GUI all-in-one installer for Linux
|
|
||||||
# Detects your distro, installs all dependencies, then installs
|
|
||||||
# zupt and zupt-gui. Run as root or with sudo.
|
|
||||||
|
|
||||||
set -e
|
# Stable entry point for the source installer. Dependency installation belongs
|
||||||
|
# to the operating-system package manager; this script performs no downloads.
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
set -Eeuo pipefail
|
||||||
ZUPT_CLI_DEB="$SCRIPT_DIR/zupt_2.2.3_amd64.deb"
|
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||||
ZUPT_GUI_DEB="$SCRIPT_DIR/zupt-gui_1.1.1_all.deb"
|
exec "$repo_root/gui/install.sh" "$@"
|
||||||
|
|
||||||
print_step() { echo ""; echo "═══ $* ═══"; }
|
|
||||||
print_err() { echo "ERROR: $*" >&2; exit 1; }
|
|
||||||
|
|
||||||
# Must be root
|
|
||||||
if [ "$EUID" -ne 0 ]; then
|
|
||||||
print_err "Run with sudo: sudo bash $0"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Detect distro
|
|
||||||
if [ -f /etc/os-release ]; then
|
|
||||||
. /etc/os-release
|
|
||||||
DISTRO="$ID"
|
|
||||||
DISTRO_LIKE="${ID_LIKE:-}"
|
|
||||||
else
|
|
||||||
print_err "Cannot detect distribution (no /etc/os-release)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_step "Detected: $PRETTY_NAME"
|
|
||||||
|
|
||||||
# 1. Install Python 3 + Qt6 binding
|
|
||||||
print_step "Step 1/3: Installing Python 3 and Qt6 binding"
|
|
||||||
case "$DISTRO" in
|
|
||||||
debian|ubuntu|linuxmint|pop)
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y python3 python3-pyqt6 || \
|
|
||||||
apt-get install -y python3 python3-pyside6
|
|
||||||
;;
|
|
||||||
fedora|rhel|centos|rocky|almalinux)
|
|
||||||
if command -v dnf >/dev/null; then
|
|
||||||
dnf install -y python3 python3-pyqt6 || dnf install -y python3 python3-pyside6
|
|
||||||
else
|
|
||||||
yum install -y python3 python3-pyqt6 || yum install -y python3 python3-pyside6
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
opensuse*|suse)
|
|
||||||
zypper install -y python3 python3-pyqt6 || zypper install -y python3 python3-PyQt6 \
|
|
||||||
|| zypper install -y python3 python3-pyside6
|
|
||||||
;;
|
|
||||||
arch|manjaro|endeavouros)
|
|
||||||
pacman -S --noconfirm python python-pyqt6 || pacman -S --noconfirm python python-pyside6
|
|
||||||
;;
|
|
||||||
alpine)
|
|
||||||
apk add python3 py3-pyqt6 || apk add python3 py3-pyside6
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
# Fallback: try pip
|
|
||||||
echo "Unknown distribution '$DISTRO'. Trying pip fallback..."
|
|
||||||
if command -v pip3 >/dev/null; then
|
|
||||||
pip3 install --break-system-packages PySide6 || pip3 install PySide6
|
|
||||||
else
|
|
||||||
print_err "No pip3 available. Install python3-pyqt6 manually for your distro."
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Verify Qt6 binding works
|
|
||||||
if ! python3 -c 'import PyQt6.QtWidgets' 2>/dev/null \
|
|
||||||
&& ! python3 -c 'import PySide6.QtWidgets' 2>/dev/null; then
|
|
||||||
print_err "Failed to install Qt6 Python binding. Install manually with your package manager."
|
|
||||||
fi
|
|
||||||
echo "✓ Python 3 + Qt6 binding installed"
|
|
||||||
|
|
||||||
# 2. Install zupt CLI
|
|
||||||
print_step "Step 2/3: Installing zupt CLI 2.2.3"
|
|
||||||
case "$DISTRO" in
|
|
||||||
debian|ubuntu|linuxmint|pop)
|
|
||||||
if [ ! -f "$ZUPT_CLI_DEB" ]; then
|
|
||||||
print_err "Cannot find $ZUPT_CLI_DEB next to this script"
|
|
||||||
fi
|
|
||||||
# Force-replace any older zupt
|
|
||||||
dpkg -i "$ZUPT_CLI_DEB" || apt-get -f install -y
|
|
||||||
;;
|
|
||||||
fedora|rhel|centos|rocky|almalinux|opensuse*|suse)
|
|
||||||
ZUPT_CLI_RPM="$SCRIPT_DIR/zupt-2.2.3-1.x86_64.rpm"
|
|
||||||
if [ -f "$ZUPT_CLI_RPM" ]; then
|
|
||||||
rpm -Uvh --force "$ZUPT_CLI_RPM"
|
|
||||||
else
|
|
||||||
print_err "RPM build not provided. Build from source tarball or install via SRPM."
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
# Fallback: tarball install
|
|
||||||
ZUPT_CLI_TAR="$SCRIPT_DIR/zupt-2.2.3-linux-x86_64.tar.gz"
|
|
||||||
if [ -f "$ZUPT_CLI_TAR" ]; then
|
|
||||||
tar -xzf "$ZUPT_CLI_TAR" -C /opt/
|
|
||||||
ln -sf /opt/zupt-2.2.3-linux-x86_64/zupt /usr/local/bin/zupt
|
|
||||||
else
|
|
||||||
print_err "No suitable installer for $DISTRO"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
echo "✓ zupt CLI installed"
|
|
||||||
|
|
||||||
# 3. Install zupt-gui
|
|
||||||
print_step "Step 3/3: Installing zupt-gui"
|
|
||||||
case "$DISTRO" in
|
|
||||||
debian|ubuntu|linuxmint|pop)
|
|
||||||
dpkg -i "$ZUPT_GUI_DEB" || apt-get -f install -y
|
|
||||||
;;
|
|
||||||
fedora|rhel|centos|rocky|almalinux|opensuse*|suse)
|
|
||||||
ZUPT_GUI_RPM="$SCRIPT_DIR/zupt-gui-1.1.1-1.noarch.rpm"
|
|
||||||
if [ -f "$ZUPT_GUI_RPM" ]; then
|
|
||||||
rpm -Uvh --force "$ZUPT_GUI_RPM"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
# Manual fallback
|
|
||||||
mkdir -p /opt/zupt-gui /usr/local/bin
|
|
||||||
cp "$SCRIPT_DIR/zupt_gui.py" /opt/zupt-gui/ 2>/dev/null || true
|
|
||||||
cat > /usr/local/bin/zupt-gui <<'WRAP'
|
|
||||||
#!/bin/sh
|
|
||||||
exec python3 /opt/zupt-gui/zupt_gui.py "$@"
|
|
||||||
WRAP
|
|
||||||
chmod +x /usr/local/bin/zupt-gui
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
echo "✓ zupt-gui installed"
|
|
||||||
|
|
||||||
print_step "Installation complete"
|
|
||||||
echo ""
|
|
||||||
echo "Run:"
|
|
||||||
echo " zupt help # CLI help"
|
|
||||||
echo " zupt-gui # Graphical interface"
|
|
||||||
echo ""
|
|
||||||
echo "If you encounter issues, check that your zupt version is correct:"
|
|
||||||
echo " zupt version # should show 2.2.3"
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,22 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# Nix flake for zupt.
|
# Nix flake for ZUPT.
|
||||||
#
|
#
|
||||||
# Usage (with flakes enabled):
|
# Usage (with flakes enabled):
|
||||||
# nix build .#zupt # build the package
|
# nix build .#zupt # build the package
|
||||||
# nix run .#zupt -- version # run zupt directly
|
# nix run .#zupt -- --version # run ZUPT directly
|
||||||
# nix develop # drop into a dev shell
|
# nix develop # drop into a dev shell
|
||||||
# nix flake check # lint the flake
|
# nix flake check # lint the flake
|
||||||
#
|
#
|
||||||
# To consume from another flake:
|
# To consume from another flake:
|
||||||
# inputs.zupt.url = "git+https://git.securityops.co/cristiancmoises/zupt?ref=v2.4.4";
|
# inputs.zupt.url = "github:cristiancmoises/zupt/v5.2.2";
|
||||||
# ...packages.x86_64-linux.default = inputs.zupt.packages.x86_64-linux.zupt;
|
# ...packages.x86_64-linux.default = inputs.zupt.packages.x86_64-linux.zupt;
|
||||||
#
|
#
|
||||||
# Reproducibility:
|
# `make dist` has its own reproducibility gate. This development flake has no
|
||||||
# * Nix already pins the source tree by hash.
|
# committed lock file and therefore makes no independent locked-output claim.
|
||||||
# * `make dist` is also reproducible (tests/test_dist_reproducible.sh).
|
|
||||||
# * Together, two independent Nix evaluations of the same flake.lock
|
|
||||||
# produce byte-identical /nix/store outputs.
|
|
||||||
|
|
||||||
{
|
{
|
||||||
description = "Zupt — post-quantum backup compression utility (C11)";
|
description = "ZUPT — post-quantum backup compression utility (C11)";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
||||||
|
|
@ -27,22 +24,25 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils }:
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system:
|
flake-utils.lib.eachSystem [ "x86_64-linux" ] (system:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs { inherit system; };
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
|
||||||
zupt = pkgs.stdenv.mkDerivation {
|
zupt = pkgs.stdenv.mkDerivation {
|
||||||
pname = "vaptvupt";
|
pname = "zupt";
|
||||||
version = "5.0.0";
|
version = "5.2.2";
|
||||||
|
|
||||||
# When publishing, replace this with `fetchurl` against the
|
# When publishing, replace this with `fetchurl` against the
|
||||||
# release tarball. For local development the flake assumes it
|
# release tarball. For local development the flake assumes it
|
||||||
# lives in the same directory as the source.
|
# lives in the same directory as the source.
|
||||||
src = ./.;
|
src = builtins.path { path = ../..; name = "zupt-source"; };
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
gcc
|
gcc
|
||||||
|
git
|
||||||
gnumake
|
gnumake
|
||||||
|
file
|
||||||
|
gnutar
|
||||||
];
|
];
|
||||||
|
|
||||||
# python3 is only used by the regression-test harness.
|
# python3 is only used by the regression-test harness.
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
# Source-only build (WITH_SDK=0): native crypto, no vendored libraries.
|
# Source-only build (WITH_SDK=0): native crypto, no vendored libraries.
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
runHook preBuild
|
runHook preBuild
|
||||||
make WITH_SDK=0 -j$NIX_BUILD_CORES
|
make WITH_SDK=0 WITH_PQBOX=0 -j$NIX_BUILD_CORES
|
||||||
runHook postBuild
|
runHook postBuild
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
|
@ -63,27 +63,30 @@
|
||||||
doCheck = true;
|
doCheck = true;
|
||||||
checkPhase = ''
|
checkPhase = ''
|
||||||
runHook preCheck
|
runHook preCheck
|
||||||
make WITH_SDK=0 check
|
make WITH_SDK=0 WITH_PQBOX=0 check
|
||||||
runHook postCheck
|
runHook postCheck
|
||||||
'';
|
'';
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
runHook preInstall
|
runHook preInstall
|
||||||
make DESTDIR=$out PREFIX= WITH_SDK=0 install
|
make PREFIX=$out WITH_SDK=0 WITH_PQBOX=0 \
|
||||||
|
INSTALL_LEGACY_ALIAS=0 install
|
||||||
|
|
||||||
# Docs
|
# Docs
|
||||||
mkdir -p $out/share/doc/vaptvupt
|
mkdir -p $out/share/doc/zupt
|
||||||
cp README.md SECURITY.md CHANGELOG.md $out/share/doc/vaptvupt/
|
cp README.md SECURITY.md CHANGELOG.md $out/share/doc/zupt/
|
||||||
|
test -f $out/share/licenses/zupt/LICENSE-BSD-3-Clause
|
||||||
|
test -f $out/share/licenses/zupt/LICENSE-CC0-1.0
|
||||||
runHook postInstall
|
runHook postInstall
|
||||||
'';
|
'';
|
||||||
|
|
||||||
meta = with pkgs.lib; {
|
meta = with pkgs.lib; {
|
||||||
description = "Post-quantum backup compression utility (ML-KEM-768 + X25519 + AES-256-CTR + HMAC-SHA256)";
|
description = "Post-quantum backup compression utility (ML-KEM-768 + X25519 + AES-256-CTR + HMAC-SHA256)";
|
||||||
homepage = "https://git.securityops.co/cristiancmoises/vaptvupt";
|
homepage = "https://github.com/cristiancmoises/zupt";
|
||||||
license = with licenses; [ agpl3Plus gpl3Plus ];
|
license = with licenses; [ agpl3Plus gpl3Plus bsd2 bsd3 cc0 ];
|
||||||
maintainers = [ ];
|
maintainers = [ ];
|
||||||
platforms = [ "x86_64-linux" "aarch64-linux" ];
|
platforms = [ "x86_64-linux" ];
|
||||||
mainProgram = "vaptvupt";
|
mainProgram = "zupt";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
|
|
|
||||||
|
|
@ -1,88 +1,220 @@
|
||||||
# openSUSE Build Service update for `home:cabelo:innovators/vaptvupt`
|
# ZUPT 5.2.2 for openSUSE Build Service
|
||||||
|
|
||||||
This directory contains the three files needed to build vaptvupt `5.0.0`
|
This directory is the upstream, source-only OBS recipe for ZUPT. It is a
|
||||||
in OBS:
|
handoff for the downstream maintainer; its presence does not mean that the
|
||||||
|
package has been submitted to or accepted by openSUSE Factory.
|
||||||
|
|
||||||
| File | Purpose |
|
Cristian Cezar Moisés, ZUPT's creator and current upstream maintainer,
|
||||||
|---------------|-------------------------------------------------------------------------|
|
prepared the 5.2.2 source, build, test, documentation, and upstream packaging
|
||||||
| `_service` | `revision` pinned to `v5.0.0`. Format unchanged (still `tar_scm`). |
|
changes in this handoff. Alessandro de Oliveira Faria (Cabelo) is credited only
|
||||||
| `vaptvupt.spec` | `Version: 5.0.0`. `License: AGPL-3.0-or-later`. `%check` calls `make check`. |
|
as the openSUSE collaborator and downstream OBS package maintainer: he reviews
|
||||||
| `vaptvupt.changes`| Changelog for the 4.x series. Older history preserved verbatim. |
|
the handoff, commits it through the portal/project he maintains, and may make
|
||||||
|
the openSUSE-side adjustments he considers necessary. This role does not
|
||||||
|
attribute upstream code or the 5.2.2 upstream changes to Cabelo.
|
||||||
|
|
||||||
## Spec notes
|
## Files and source policy
|
||||||
|
|
||||||
1. **License** — `AGPL-3.0-or-later` (dual-licensed AGPL-3.0-or-later
|
| File | Purpose |
|
||||||
+ commercial).
|
|---|---|
|
||||||
|
| `_service` | Fetch the immutable `v5.2.2` tag and create `Source0` at build time. |
|
||||||
|
| `zupt.spec` | Build and test the CLI with optional external system integrations disabled. |
|
||||||
|
| `zupt.changes` | openSUSE-format package history. |
|
||||||
|
| `source-audit.sh` | Handoff wrapper for the repository scanner; run it from the complete handoff tree. |
|
||||||
|
|
||||||
2. **No BuildRequires beyond the toolchain** — the default build needs
|
The source service uses `obs_scm`, with Git submodules and Git LFS explicitly
|
||||||
only `gcc gzip make` (plus `libm`/`pthread` from glibc). There are
|
disabled. Its primary URL is the canonical upstream:
|
||||||
**no system library BuildRequires**. The repository is source-only:
|
|
||||||
the previously vendored `libzuptsdk.so` and `libpqvaptvupt.so` have
|
|
||||||
been removed from the tree, `%build` and `%install` run with
|
|
||||||
`WITH_SDK=0`, and `%files` no longer lists any `.so`. The package
|
|
||||||
installs no shared library. Do not add system crypto BuildRequires.
|
|
||||||
|
|
||||||
The optional SDK modes (`--pq-sdk`, `--pq-box`) and the Argon2id KDF
|
```text
|
||||||
require an upstream `make WITH_SDK=1` build linked against the
|
https://github.com/cristiancmoises/zupt.git
|
||||||
separately distributed `libzuptsdk`/`libpqvaptvupt` libraries. They
|
|
||||||
are not part of this package.
|
|
||||||
|
|
||||||
3. **`%check` target** — the s390x branch falls back to `test-vectors`;
|
|
||||||
other architectures run `make check`. This exercises the HMAC tamper
|
|
||||||
detection, archive-integrity trailer, byte-level integrity preface
|
|
||||||
AAD, default-KDF, auth-fail, and encrypted-comment suites, the
|
|
||||||
NIST/RFC vectors (SHA-256, SHA-3, ML-KEM-768, AES-256-CTR, HMAC,
|
|
||||||
X25519, PBKDF2), and the path-traversal, argument-order, and
|
|
||||||
block-swap regressions.
|
|
||||||
|
|
||||||
The default password KDF is **PBKDF2-SHA256** (600k iterations).
|
|
||||||
Argon2id test vectors run only in a `WITH_SDK=1` build and are not
|
|
||||||
checked here.
|
|
||||||
|
|
||||||
4. **URLs** — the `URL:` field points at the canonical project URL
|
|
||||||
`https://git.securityops.co/cristiancmoises/vaptvupt`. The `_service`
|
|
||||||
file still fetches from GitHub
|
|
||||||
(`https://github.com/cristiancmoises/vaptvupt`), which is what the
|
|
||||||
existing `tar_scm` configuration uses in OBS.
|
|
||||||
|
|
||||||
## How to apply
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# 1. Check out the package
|
|
||||||
osc checkout home:cabelo:innovators vaptvupt
|
|
||||||
cd home:cabelo:innovators/vaptvupt
|
|
||||||
|
|
||||||
# 2. Drop the new files in (assuming this README is at
|
|
||||||
# /path/to/vaptvupt-source/packaging/opensuse/README.md)
|
|
||||||
cp /path/to/vaptvupt-source/packaging/opensuse/_service .
|
|
||||||
cp /path/to/vaptvupt-source/packaging/opensuse/vaptvupt.spec .
|
|
||||||
cp /path/to/vaptvupt-source/packaging/opensuse/vaptvupt.changes .
|
|
||||||
|
|
||||||
# 3. Trigger the service locally to fetch v5.0.0 from GitHub
|
|
||||||
osc service runall
|
|
||||||
# Produces vaptvupt-5.0.0.tar.gz in the current directory.
|
|
||||||
|
|
||||||
# 4. (Optional) Local build to verify before committing
|
|
||||||
osc build openSUSE_Tumbleweed x86_64
|
|
||||||
|
|
||||||
# 5. Commit upstream
|
|
||||||
osc status # confirm vaptvupt-5.0.0.tar.gz is staged alongside the
|
|
||||||
# three text files
|
|
||||||
osc commit -m "Update to 5.0.0"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notes for future updates
|
`obs_scm` stores an `.obscpio` plus `.obsinfo`. The `tar` and `recompress`
|
||||||
|
services reconstruct `zupt-5.2.2.tar.gz` inside the build environment, which
|
||||||
|
matches `Source0` in the spec.
|
||||||
|
|
||||||
* The `_service` `revision` is pinned to `v5.0.0`. To track a new
|
This source policy does not prohibit separately built release-page packages.
|
||||||
release, edit that one line and re-run `osc service runall`.
|
The upstream 5.2.2 gates may publish the CLI source tarball, DEB, binary RPM,
|
||||||
* The spec's `Version:` field is hard-coded — when you bump `_service`
|
SRPM, notice-bearing Linux tar.xz, Windows ZIP, and macOS DMG, together with a
|
||||||
`revision`, also bump `Version:` to match.
|
GUI DEB, noarch RPM, GUI SRPM, and source-only portable GUI ZIP after each
|
||||||
* `BuildRequires` is intentionally minimal (`gcc gzip make`). vaptvupt
|
format-specific test succeeds. None of those files is an OBS `Source0` input
|
||||||
has no external library dependencies in the default build; do not add
|
or belongs in Git. AppImage and bare executables remain excluded: the former
|
||||||
system crypto BuildRequires.
|
lacks an audited runtime source/relink handoff, while the latter does not carry
|
||||||
|
the required license and notice payload beside the program.
|
||||||
|
|
||||||
## Reporting issues
|
## License and bundled codec
|
||||||
|
|
||||||
* Upstream bugs: https://git.securityops.co/cristiancmoises/vaptvupt
|
The resulting executable combines the AGPL-3.0-or-later application with the
|
||||||
* openSUSE packaging bugs: https://bugs.opensuse.org/
|
GPL-3.0-or-later VaptVupt codec, adapted BSD-2-Clause XXH64 routines, and
|
||||||
* Cabelo's OBS project: https://build.opensuse.org/project/show/home:cabelo:innovators
|
CC0-1.0 pq-crystals/kyber-derived ML-KEM portions, plus BSD-3-Clause
|
||||||
|
curve25519-donna-derived X25519 portions, so the RPM uses:
|
||||||
|
|
||||||
|
```text
|
||||||
|
AGPL-3.0-or-later AND GPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND CC0-1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
The bundled codec is VaptVupt codec tag `v2.65.3`. It was integrated into this
|
||||||
|
repository by commit `59f9ebc59ea13c6edf1d199ca795cdbf00e62226` and is declared
|
||||||
|
as `bundled(vaptvupt-codec) = 2.65.3`. That integration commit records the local
|
||||||
|
ANS safe-zone reserve patch applied on top of the upstream tag. The package
|
||||||
|
retains all license and notice files, including Yann Collet's xxHash notice;
|
||||||
|
it does not claim that the codec is unbundled.
|
||||||
|
|
||||||
|
## Optional SDK and PQBOX integrations
|
||||||
|
|
||||||
|
The OBS package always builds with:
|
||||||
|
|
||||||
|
```text
|
||||||
|
WITH_SDK=0 WITH_PQBOX=0
|
||||||
|
```
|
||||||
|
|
||||||
|
The resulting CLI retains the in-tree password, ML-KEM-768, X25519 and hybrid
|
||||||
|
features. It does not enable the optional libvuptsdk-backed Argon2id/`--pq-sdk`
|
||||||
|
integration or the separate libpqvaptvupt-backed `--pq-box` integration. Those
|
||||||
|
options may only be enabled in a future package after their complete source or
|
||||||
|
system development packages, licenses, ABI and dependencies have been audited.
|
||||||
|
The build does not download dependencies and never loads a repository-local
|
||||||
|
`.so`, `.a` or `.o` fallback.
|
||||||
|
|
||||||
|
## Archive integrity and compatibility in 5.2.2
|
||||||
|
|
||||||
|
New encrypted archives bind every DATA and DEDUP_REF frame to its logical
|
||||||
|
position. An authenticated reference also carries the authenticated position of
|
||||||
|
the source DATA frame, and new disk archives use flag-gated index/content-hash
|
||||||
|
metadata. The on-disk version byte remains 1.6, but an older reader is not
|
||||||
|
claimed to accept every new 5.2.2 encoding.
|
||||||
|
|
||||||
|
The packaged `extract`, `list`, `test`, and `disk restore` paths require an
|
||||||
|
archive-integrity trailer by default, without trusting unauthenticated header
|
||||||
|
flags. `--allow-legacy-no-ait` is accepted only by those commands for recovery
|
||||||
|
of a known, trusted pre-AIT archive and emits a downgrade warning. `info` merely
|
||||||
|
reports unauthenticated framing and apparent AIT presence; it does not validate
|
||||||
|
the trailer or contents. Package documentation must not recommend the override
|
||||||
|
for untrusted input or present `info` success as an integrity result.
|
||||||
|
|
||||||
|
The separate v5.2.1 compatibility claim is narrow: an actual
|
||||||
|
password-encrypted, deduplicated DATA/DATA/REF/DATA disk archive created from the
|
||||||
|
immutable v5.2.1 tag is stored as hexadecimal text with its source and SHA-256
|
||||||
|
provenance. The 5.2.2 reader reconstructs the legacy linear block-AAD sequence,
|
||||||
|
lists, tests, extracts, and restores its input byte-exact through the
|
||||||
|
fixed-width legacy disk-index parser. This does not cover every historical mode
|
||||||
|
and must be rerun on the final candidate before it is promoted as a release
|
||||||
|
gate.
|
||||||
|
|
||||||
|
Disk restore also snapshots the measured archive into a private scratch file
|
||||||
|
before opening the destination, then validates and restores from that same
|
||||||
|
stream. An invalid `ZUPT_TMPDIR` override (or the compatibility fallback
|
||||||
|
`VAPTVUPT_TMPDIR`) and an unknown or insufficient raw-device capacity fail
|
||||||
|
before the first target write. The package check covers
|
||||||
|
the unprivileged unknown-capacity path; its loop-device size regression is
|
||||||
|
reported `SKIP`, not `PASS`, when the builder cannot create a loop device.
|
||||||
|
|
||||||
|
## Migration from the former package name
|
||||||
|
|
||||||
|
The main package is named `zupt` and installs only `/usr/bin/zupt`, its man
|
||||||
|
page, and its completions. The spec has a versioned `Provides: vaptvupt` and
|
||||||
|
`Obsoletes: vaptvupt` so an installed package under the former public name can
|
||||||
|
upgrade cleanly. It intentionally does not claim or install a second
|
||||||
|
`/usr/bin/vaptvupt` executable. The bundled codec and optional library keep
|
||||||
|
their established VaptVupt identifiers because those are compatibility-facing
|
||||||
|
API names, not the application package name.
|
||||||
|
|
||||||
|
## Local validation workflow
|
||||||
|
|
||||||
|
Run these commands in an OBS package checkout, not in the upstream Git tree:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
xmllint --noout _service
|
||||||
|
osc service manualrun
|
||||||
|
rpmspec -P zupt.spec >/dev/null
|
||||||
|
spec-cleaner --diff zupt.spec
|
||||||
|
osc build --clean --keep-pkgs="$PWD/.osc-build-results" \
|
||||||
|
openSUSE_Tumbleweed x86_64
|
||||||
|
rpmlint .osc-build-results/*.rpm
|
||||||
|
```
|
||||||
|
|
||||||
|
`osc service manualrun` materializes the service marked `manual` (the pinned
|
||||||
|
SCM input). The tarball itself is
|
||||||
|
reconstructed by the build-time services. Neither `%build` nor `%check` may
|
||||||
|
access the network.
|
||||||
|
|
||||||
|
For a source RPM check outside OBS, place the service-produced
|
||||||
|
`zupt-5.2.2.tar.gz` next to the spec and use a disposable RPM build tree:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rpm_top=$(mktemp -d)
|
||||||
|
trap 'rm -rf -- "$rpm_top"' EXIT
|
||||||
|
mkdir -p "$rpm_top"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
||||||
|
rpmbuild --define "_topdir $rpm_top" --define "_sourcedir $PWD" \
|
||||||
|
-bs zupt.spec
|
||||||
|
```
|
||||||
|
|
||||||
|
After building, inspect the RPM contents and dependencies, run `rpmlint`, then
|
||||||
|
install it in a disposable openSUSE environment and execute
|
||||||
|
`scripts/test-installed-zupt.sh`. Do not claim a repository or architecture
|
||||||
|
as supported until its build and installed smoke test have actually passed.
|
||||||
|
|
||||||
|
## Validation matrix for this handoff
|
||||||
|
|
||||||
|
The local results below were produced on 2026-08-24 from the 5.2.2 candidate
|
||||||
|
snapshot captured for the packaging run, in a disposable openSUSE Tumbleweed
|
||||||
|
20260822 x86_64 container. This matrix was documented afterward, so the results
|
||||||
|
validate that captured snapshot, not the later documentation edit, a future
|
||||||
|
commit or a tag. Commit- and tag-dependent checks must be repeated after the
|
||||||
|
final commit; the validation tarball checksum below is not a release checksum.
|
||||||
|
`SKIP` is not success.
|
||||||
|
|
||||||
|
| Gate | Result | Evidence |
|
||||||
|
|---|---|---|
|
||||||
|
| `_service` XML syntax | PASS | `xmllint --noout`; installed service definitions and parameters also exercised locally. |
|
||||||
|
| ShellCheck for packaging, export, source-policy, and security regression scripts | PASS | ShellCheck 0.10.0 returned zero for the scripts listed in the CI source-policy job, including the scanner and new archive/disk regressions; repeat after the final commit/tag. |
|
||||||
|
| Upstream source-only scanner and adversarial scanner tests | PASS | Clean snapshot: 191 files; OBS tar: 191 files/1 archive; SRPM tree: 193 files/1 archive; 29 positive/negative scanner regressions passed. |
|
||||||
|
| Reproducible source archive (two builds, same SHA-256) | PASS | Two local `obs_scm`/`tar`/`recompress` runs were byte-identical (`39e59f5e...`, validation only; regenerate after the real tag). |
|
||||||
|
| Upstream build, `make check`, and `make test-all` | SKIP | The real RPM `%check`/`make check` passed; an exact-candidate `make test-all` result was not produced by this packaging run. |
|
||||||
|
| Positional DATA/DEDUP_REF AAD and mandatory-AIT regressions | PASS | `%check` passed AIT removal, F-09 preface, DATA/REF reorder/replay, little-endian, varint and atomic-output regressions. |
|
||||||
|
| v5.2.1 encrypted+dedup disk compatibility | PASS | Working-tree candidate decoded the textual 718-byte v5.2.1 DATA/DATA/REF/DATA fixture, then `list`, `test`, generic extraction, and byte-exact disk restore passed; repeat after the final commit/tag. |
|
||||||
|
| `rpmspec` parse | PASS | Both `rpmspec -P` and `rpmspec --parse` returned zero; Source0 resolved to `zupt-5.2.2.tar.gz`. |
|
||||||
|
| `spec-cleaner` | PASS | Version 1.2.4+2 returned zero and proposed no diff. |
|
||||||
|
| `rpmbuild` source and binary RPM | PASS | `rpmbuild -bs` and `-ba` passed from the service-generated Source0 with the openSUSE `.changes` conversion. |
|
||||||
|
| `rpmlint` main RPM + SRPM | PASS | 0 errors and one `invalid-url Source0` warning for the service-generated local Source0; no `rpmlintrc` or suppression was added. |
|
||||||
|
| `rpmlint` including automatic debug packages | FAIL | `debugsource: no-binary` error and expected `debuginfo: unstripped-binary-or-object` warning from the complete generated package set; debug packages were not disabled or suppressed. |
|
||||||
|
| `osc service` | PASS | Installed `obs_scm` 0.12.4, `tar` 0.12.4 and `recompress` 0.5.2 produced the correctly named source tar locally; canonical tag fetch remains tag-dependent. |
|
||||||
|
| Tumbleweed x86_64 local build/install/round trip/uninstall | PASS | Tumbleweed 20260822 container: RPM `%check`, root and `nobody` installed tests, content/hardening audit and clean uninstall passed. This is not an OBS/Factory result. |
|
||||||
|
| Official OBS `osc build` invocation | FAIL | The command reached `https://api.opensuse.org` but returned HTTP 401 because no OBS credentials are configured. |
|
||||||
|
| Factory/Tumbleweed x86_64 OBS validation | SKIP | The failed authenticated `osc build` invocation produced no Factory build result; local Tumbleweed evidence is not promoted to Factory evidence. |
|
||||||
|
| aarch64, ppc64le, s390x, riscv64 | SKIP | No build evidence yet. |
|
||||||
|
| Leap and SLE | SKIP | No build evidence yet. |
|
||||||
|
|
||||||
|
`SKIP` is not success. Factory/Tumbleweed x86_64 remains the primary downstream
|
||||||
|
gate.
|
||||||
|
|
||||||
|
## Handoff procedure for Alessandro/Cabelo
|
||||||
|
|
||||||
|
1. Upstream creates and verifies the annotated `v5.2.2` tag only after all
|
||||||
|
mandatory gates pass.
|
||||||
|
2. With Git, `file`, bsdtar, tar, zip, unzip and SHA-256 tools installed, run
|
||||||
|
`scripts/export-opensuse-package.sh v5.2.2`. Verify the reported ZIP and
|
||||||
|
SHA-256 outside the Git index. The handoff includes both
|
||||||
|
`packaging/opensuse/source-audit.sh` and its required
|
||||||
|
`scripts/check-source-only.sh`; keep that relative layout while auditing.
|
||||||
|
3. Check out the OBS package:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
osc checkout home:cabelo:innovators zupt
|
||||||
|
cd home:cabelo:innovators/zupt
|
||||||
|
```
|
||||||
|
|
||||||
|
4. From the extracted handoff root, run
|
||||||
|
`packaging/opensuse/source-audit.sh --archive /path/to/zupt-5.2.2.tar.gz`.
|
||||||
|
Then copy `_service`, `zupt.spec`, `zupt.changes` and `README.md`
|
||||||
|
into the flat OBS package checkout. The audit wrapper is not an OBS build
|
||||||
|
source and must not be copied without its companion `scripts/` directory.
|
||||||
|
5. Run the local validation workflow above, including the installed round-trip
|
||||||
|
test. Build every repository and architecture enabled in the OBS project;
|
||||||
|
record failures or unavailable gates as such.
|
||||||
|
6. Review `osc diff`, confirm that no RPM or other binary was added as a source,
|
||||||
|
and commit to OBS only after the required gates pass.
|
||||||
|
|
||||||
|
For future releases, increment the stable patch version, create a new immutable
|
||||||
|
tag, update the matching revision/version in `_service`, spec and changes, run
|
||||||
|
the source-only scanner, regenerate the handoff, and repeat every OBS gate.
|
||||||
|
Never move an existing tag or consume forge release binaries as `Source0`.
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
||||||
<services>
|
<services>
|
||||||
<service name="tar_scm" mode="manual">
|
<service name="obs_scm" mode="manual">
|
||||||
<param name="url">https://github.com/cristiancmoises/vaptvupt</param>
|
<param name="url">https://github.com/cristiancmoises/zupt.git</param>
|
||||||
<param name="scm">git</param>
|
<param name="scm">git</param>
|
||||||
<param name="revision">v5.0.0</param>
|
<param name="revision">refs/tags/v5.2.2</param>
|
||||||
<param name="versionformat">@PARENT_TAG@</param>
|
<param name="versionformat">@PARENT_TAG@</param>
|
||||||
<param name="versionrewrite-pattern">v(.*)</param>
|
<param name="versionrewrite-pattern">^v(.*)$</param>
|
||||||
<param name="submodules">enable</param>
|
<param name="versionrewrite-replacement">\1</param>
|
||||||
<param name="filename">vaptvupt</param>
|
<param name="filename">zupt</param>
|
||||||
</service>
|
<param name="submodules">disable</param>
|
||||||
<service name="recompress" mode="manual">
|
<param name="lfs">disable</param>
|
||||||
<param name="file">*.tar</param>
|
</service>
|
||||||
<param name="compression">gz</param>
|
<service name="tar" mode="buildtime"/>
|
||||||
</service>
|
<service name="recompress" mode="buildtime">
|
||||||
<service name="set_version" mode="manual"/>
|
<param name="file">*.tar</param>
|
||||||
|
<param name="compression">gz</param>
|
||||||
|
</service>
|
||||||
</services>
|
</services>
|
||||||
|
|
|
||||||
14
packaging/opensuse/source-audit.sh
Executable file
14
packaging/opensuse/source-audit.sh
Executable file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
|
||||||
|
SCANNER=$SCRIPT_DIR/../../scripts/check-source-only.sh
|
||||||
|
|
||||||
|
if [[ ! -x $SCANNER ]]; then
|
||||||
|
printf 'ERROR: source-only scanner is missing or not executable: %s\n' "$SCANNER" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$SCANNER" "$@"
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
#
|
|
||||||
# spec file for package vaptvupt
|
|
||||||
#
|
|
||||||
# Copyright (c) 2026 SUSE LLC
|
|
||||||
# Copyright (c) 2026 Alessandro de Oliveira Faria (A.K.A CABELO) <cabelo@opensuse.org>
|
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés <zupt@riseup.net> (upstream)
|
|
||||||
#
|
|
||||||
# All modifications and additions to the file contributed by third parties
|
|
||||||
# remain the property of their copyright owners, unless otherwise agreed
|
|
||||||
# upon. The license for this file, and modifications and additions to the
|
|
||||||
# file, is the same license as for the pristine package itself (unless the
|
|
||||||
# license for the pristine package is not an Open Source License, in which
|
|
||||||
# case the license is the MIT License). An "Open Source License" is a
|
|
||||||
# license that conforms to the Open Source Definition (Version 1.9)
|
|
||||||
# published by the Open Source Initiative.
|
|
||||||
|
|
||||||
# Please submit bugfixes or comments via https://bugs.opensuse.org/
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
Name: vaptvupt
|
|
||||||
Version: 5.2.1
|
|
||||||
Release: 0
|
|
||||||
Summary: Post-quantum backup compression with AES-256 + ML-KEM-768 hybrid encryption
|
|
||||||
License: AGPL-3.0-or-later
|
|
||||||
Group: Productivity/Archiving/Compression
|
|
||||||
URL: https://git.securityops.co/cristiancmoises/vaptvupt
|
|
||||||
Source0: %{name}-%{version}.tar.gz
|
|
||||||
BuildRequires: gcc
|
|
||||||
BuildRequires: gzip
|
|
||||||
BuildRequires: make
|
|
||||||
|
|
||||||
# v3.0.0 renamed the project Zupt -> VaptVupt (prior INPI Brasil
|
|
||||||
# trademark on "Zupt"). Cleanly supersede any installed zupt package;
|
|
||||||
# the binary still installs a /usr/bin/zupt compatibility symlink.
|
|
||||||
Provides: zupt = %{version}-%{release}
|
|
||||||
Obsoletes: zupt < 3.0.0
|
|
||||||
|
|
||||||
%description
|
|
||||||
VaptVupt (formerly Zupt; renamed in v3.0.0 due to a prior INPI Brasil
|
|
||||||
trademark on the name "Zupt") compresses and encrypts backup archives.
|
|
||||||
LZ + ANS compression (VaptVupt codec, ~2-3 GB/s decompression on x86_64
|
|
||||||
with AVX2 / aarch64 with NEON), AES-256-CTR + HMAC-SHA256 per-block
|
|
||||||
authenticated encryption, multi-threaded, with ML-KEM-768 + X25519
|
|
||||||
post-quantum hybrid key encapsulation (FIPS 203 + RFC 7748) via --pq.
|
|
||||||
|
|
||||||
This package builds entirely from source with no external library
|
|
||||||
dependency. The password KDF is PBKDF2-SHA256 (600k iterations). The
|
|
||||||
optional libzuptsdk-backed modes (Argon2id KDF, --pq-sdk, --pq-box) are
|
|
||||||
not built here; they require an upstream WITH_SDK=1 build against the
|
|
||||||
separately distributed libzuptsdk/libpqvaptvupt.
|
|
||||||
|
|
||||||
Pure C11, ~5,000 lines of core code. Constant-time cryptographic
|
|
||||||
primitives are formally verified with Jasmin on x86_64
|
|
||||||
(zupt_mac_verify_ct, zupt_ct_select_32); a clean C fallback runs on
|
|
||||||
aarch64 and other architectures.
|
|
||||||
|
|
||||||
%prep
|
|
||||||
%autosetup -p1
|
|
||||||
chmod +x tests/*.sh
|
|
||||||
|
|
||||||
%build
|
|
||||||
%make_build V=1 WITH_SDK=0 \
|
|
||||||
CFLAGS="%{optflags} -fPIE -Wall -Wextra -std=c11 -Iinclude -Isrc" \
|
|
||||||
LDFLAGS="%{?build_ldflags} -pie" \
|
|
||||||
LDLIBS="-lm -lpthread"
|
|
||||||
|
|
||||||
%check
|
|
||||||
# `make check` is the distro-safe subset added in 2.4.8: runs the
|
|
||||||
# security-critical regressions (F-06 HMAC, F-08 AIT, F-09 byte
|
|
||||||
# integrity, F-10 KDF, F-11 auth-fail, F-12 comments) plus NIST/RFC
|
|
||||||
# vectors. Skips threaded and dist-reproducibility tests that are
|
|
||||||
# sensitive to build-host environment.
|
|
||||||
#
|
|
||||||
# On s390x, fall back to just the vector tests (Jasmin assembly is
|
|
||||||
# x86_64-only; threading harness has been flaky on big-endian).
|
|
||||||
%ifarch s390x
|
|
||||||
%make_build V=1 WITH_SDK=0 \
|
|
||||||
CFLAGS="%{optflags} -fPIE -Wall -Wextra -std=c11 -Iinclude -Isrc" \
|
|
||||||
LDFLAGS="%{?build_ldflags} -pie" \
|
|
||||||
LDLIBS="-lm -lpthread" \
|
|
||||||
test-vectors
|
|
||||||
./test_vectors
|
|
||||||
%else
|
|
||||||
%make_build V=1 WITH_SDK=0 \
|
|
||||||
CFLAGS="%{optflags} -fPIE -Wall -Wextra -std=c11 -Iinclude -Isrc" \
|
|
||||||
LDFLAGS="%{?build_ldflags} -pie" \
|
|
||||||
LDLIBS="-lm -lpthread" \
|
|
||||||
check
|
|
||||||
%endif
|
|
||||||
|
|
||||||
%install
|
|
||||||
%make_install WITH_SDK=0 PREFIX=%{_prefix}
|
|
||||||
|
|
||||||
%files
|
|
||||||
%license LICENSE
|
|
||||||
%doc README.md SECURITY.md CHANGELOG.md
|
|
||||||
%{_bindir}/vaptvupt
|
|
||||||
%{_bindir}/zupt
|
|
||||||
%{_datadir}/bash-completion/completions/vaptvupt
|
|
||||||
%{_datadir}/bash-completion/completions/zupt
|
|
||||||
%{_datadir}/zsh/site-functions/_vaptvupt
|
|
||||||
%{_datadir}/zsh/site-functions/_zupt
|
|
||||||
%{_datadir}/fish/vendor_completions.d/vaptvupt.fish
|
|
||||||
%{_mandir}/man1/vaptvupt.1%{?ext_man}
|
|
||||||
%{_mandir}/man1/zupt.1%{?ext_man}
|
|
||||||
|
|
||||||
%changelog
|
|
||||||
* Sat Jul 11 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.1.0-1
|
|
||||||
- Codec 2.65.0; large compression-ratio gains (auto format_v2 + level-scaled
|
|
||||||
block window); --dedup keeps a small block; GUI compress-hang and
|
|
||||||
job-completion-crash fixes. Wire format unchanged (v1.6).
|
|
||||||
|
|
||||||
|
|
@ -1,3 +1,69 @@
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
Mon Aug 31 00:00:00 UTC 2026 - Cristian Cezar Moisés <sac@securityops.co>
|
||||||
|
|
||||||
|
- Update to 5.2.2:
|
||||||
|
* Convert the upstream and OBS inputs to a source-only release: remove
|
||||||
|
precompiled library and object inputs and reject their reintroduction with
|
||||||
|
reusable source-archive auditing.
|
||||||
|
* Build with WITH_SDK=0 and WITH_PQBOX=0. The optional integrations now
|
||||||
|
require an explicit source or packaged system dependency and never use a
|
||||||
|
private precompiled fallback.
|
||||||
|
* Preserve portable compiler and linker flags, architecture-local optimized
|
||||||
|
translation units, scalar fallbacks, parallel build, and staged DESTDIR
|
||||||
|
installation.
|
||||||
|
* Build the packaged executable as PIE with full RELRO/NOW and a
|
||||||
|
non-executable stack while preserving automatic debuginfo generation and
|
||||||
|
avoiding manual stripping or RPATH/RUNPATH.
|
||||||
|
* Update OBS source services to obs_scm pinned to the immutable v5.2.2 tag;
|
||||||
|
disable submodules and Git LFS and create the compressed tarball at build
|
||||||
|
time.
|
||||||
|
* Run the real upstream check target from the RPM check phase without
|
||||||
|
architecture-specific test suppression.
|
||||||
|
* Harden archive extraction against traversal, symlink/hardlink and Windows
|
||||||
|
reparse-point races; publish only fully size/checksum-verified temporary
|
||||||
|
output and add structurally valid hostile-archive regression fixtures.
|
||||||
|
* Reject normal, solid, and disk-backup output aliases of an input file,
|
||||||
|
including alternate spellings, hardlinks, and symlinks, before creating the
|
||||||
|
output; --force cannot bypass this data-loss guard.
|
||||||
|
* Snapshot disk-restore input privately before opening its destructive
|
||||||
|
destination and restore from the same validated stream. Reject raw devices
|
||||||
|
whose capacity is unknown or smaller than the image before the first write.
|
||||||
|
* Enforce DATA frame types across serial, threaded, solid, test, and disk
|
||||||
|
readers, and retain the exact encrypted+dedup AAD sequence used by v5.2.1.
|
||||||
|
Test an actual v5.2.1 password-encrypted DATA/DATA/REF/DATA disk fixture
|
||||||
|
through list, test, generic extraction, and disk restore.
|
||||||
|
* Use random private benchmark scratch directories and remove them without
|
||||||
|
following links instead of using a predictable process-ID path.
|
||||||
|
* Package the AGPL-3.0-or-later application together with the bundled
|
||||||
|
GPL-3.0-or-later VaptVupt codec 2.65.3 and the BSD-2-Clause XXH64-derived
|
||||||
|
routines; preserve all applicable notices.
|
||||||
|
* Rename the application and package back to ZUPT/zupt. Install only the
|
||||||
|
zupt command and add versioned Provides/Obsoletes for migration from the
|
||||||
|
former vaptvupt package without shipping a duplicate executable.
|
||||||
|
* Add the source-only openSUSE handoff/export workflow and validation matrix.
|
||||||
|
* Add explicit password prompt, file, and inherited-descriptor inputs.
|
||||||
|
* Validate the source audit, rpmbuild -bs/-ba, the complete RPM check phase,
|
||||||
|
package contents and dependencies, installed round trips, and clean
|
||||||
|
uninstall in a disposable openSUSE Tumbleweed 20260822 x86_64 container.
|
||||||
|
OBS/Factory, other architectures, Leap, and SLE remain separate unexecuted
|
||||||
|
downstream gates and are not claimed by this validation.
|
||||||
|
* Correct the licensing record without revoking historical MIT grants present
|
||||||
|
in earlier repository revisions; current files follow current SPDX notices.
|
||||||
|
* Correct the stale public-domain statement for XXH64-derived code and retain
|
||||||
|
Yann Collet's BSD-2-Clause copyright, conditions, and disclaimer.
|
||||||
|
* Record the CC0-1.0 option and provenance for pq-crystals/kyber-derived
|
||||||
|
ML-KEM portions, the BSD-3-Clause curve25519-donna origin of native X25519
|
||||||
|
portions, and the public-domain LZMA SDK origin of the x86 BCJ code.
|
||||||
|
* Keep AppImage outside the 5.2.2 promoted set until its static runtime has a
|
||||||
|
complete license/source-relink handoff; publish Windows only as a ZIP with
|
||||||
|
the executable and notices.
|
||||||
|
* Gate notice-bearing Linux tar.xz and Windows/macOS CLI bundles plus GUI
|
||||||
|
DEB, noarch RPM, source RPM, and source-only portable ZIP artifacts; keep
|
||||||
|
bare executables out of the promoted set.
|
||||||
|
* Qualify historical formal-verification and constant-time wording: current
|
||||||
|
source review and runtime regressions are not a proof for every compiler,
|
||||||
|
CPU, or final package binary.
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
-------------------------------------------------------------------
|
||||||
Fri Jul 10 18:00:00 UTC 2026 - Alessandro de Oliveira Faria <cabelo@opensuse.org>
|
Fri Jul 10 18:00:00 UTC 2026 - Alessandro de Oliveira Faria <cabelo@opensuse.org>
|
||||||
|
|
||||||
|
|
@ -244,10 +310,10 @@ Tue May 26 02:27:34 UTC 2026 - Alessandro de Oliveira Faria <cabelo@opensuse.org
|
||||||
Tue May 26 00:43:52 UTC 2026 - Alessandro de Oliveira Faria <cabelo@opensuse.org>
|
Tue May 26 00:43:52 UTC 2026 - Alessandro de Oliveira Faria <cabelo@opensuse.org>
|
||||||
|
|
||||||
- Update to 3.0.1
|
- Update to 3.0.1
|
||||||
* GUI license cleanup: removed MIT credit line from the about
|
* GUI license metadata changed to AGPL-3.0-or-later for the then-current
|
||||||
panel; gui/LICENSE-GUI replaced (was MIT) with AGPL-3.0-or-later
|
source. The original entry incorrectly denied earlier MIT grants; the
|
||||||
to match the source SPDX header. The GUI was never actually
|
5.2.2 erratum records that they remain valid for the exact historical
|
||||||
released under MIT — that was a templating mistake.
|
material distributed under them.
|
||||||
* GUI version-string parsing bug fix (the replace("zupt ", ...)
|
* GUI version-string parsing bug fix (the replace("zupt ", ...)
|
||||||
substring also matched inside the v3.0.0 parenthetical). Window
|
substring also matched inside the v3.0.0 parenthetical). Window
|
||||||
title, splash header, status bar and about-panel hero number now
|
title, splash header, status bar and about-panel hero number now
|
||||||
|
|
@ -296,7 +362,7 @@ Sun May 24 13:08:04 UTC 2026 - Alessandro de Oliveira Faria <cabelo@opensuse.org
|
||||||
OBS %check (no `make clean` mid-stream, no threading-flaky
|
OBS %check (no `make clean` mid-stream, no threading-flaky
|
||||||
tests). Spec now calls `make check` on x86_64/aarch64.
|
tests). Spec now calls `make check` on x86_64/aarch64.
|
||||||
* License field corrected: AGPL-3.0-or-later (was MIT in 1.5.x).
|
* License field corrected: AGPL-3.0-or-later (was MIT in 1.5.x).
|
||||||
Project is dual-licensed AGPL-3.0-or-later + commercial.
|
Commercial-terms inquiry information was documented separately.
|
||||||
* Upstream URL updated to git.securityops.co.
|
* Upstream URL updated to git.securityops.co.
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
-------------------------------------------------------------------
|
||||||
83
packaging/opensuse/zupt.spec
Normal file
83
packaging/opensuse/zupt.spec
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
#
|
||||||
|
# spec file for package zupt
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
# Copyright (c) 2026 SUSE LLC
|
||||||
|
# Copyright (c) 2026 Alessandro de Oliveira Faria (A.K.A. Cabelo) <cabelo@opensuse.org>
|
||||||
|
# Alessandro's attribution is for downstream openSUSE/OBS packaging only.
|
||||||
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés <sac@securityops.co> (upstream)
|
||||||
|
#
|
||||||
|
# All modifications and additions to the file contributed by third parties
|
||||||
|
# remain the property of their copyright owners, unless otherwise agreed
|
||||||
|
# upon. The license for this file, and modifications and additions to the
|
||||||
|
# file, is the same license as for the pristine package itself (unless the
|
||||||
|
# license for the pristine package is not an Open Source License, in which
|
||||||
|
# case the license is the MIT License). An "Open Source License" is a
|
||||||
|
# license that conforms to the Open Source Definition (Version 1.9)
|
||||||
|
# published by the Open Source Initiative.
|
||||||
|
#
|
||||||
|
|
||||||
|
Name: zupt
|
||||||
|
Version: 5.2.2
|
||||||
|
Release: 0
|
||||||
|
Summary: Backup compression with authenticated and post-quantum encryption
|
||||||
|
License: AGPL-3.0-or-later AND GPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND CC0-1.0
|
||||||
|
URL: https://github.com/cristiancmoises/zupt
|
||||||
|
Source0: %{name}-%{version}.tar.gz
|
||||||
|
BuildRequires: bash
|
||||||
|
BuildRequires: coreutils
|
||||||
|
BuildRequires: diffutils
|
||||||
|
BuildRequires: file
|
||||||
|
BuildRequires: findutils
|
||||||
|
BuildRequires: gawk
|
||||||
|
BuildRequires: gcc
|
||||||
|
BuildRequires: git-core
|
||||||
|
BuildRequires: grep
|
||||||
|
BuildRequires: gzip
|
||||||
|
BuildRequires: make
|
||||||
|
BuildRequires: python3-base
|
||||||
|
BuildRequires: sed
|
||||||
|
BuildRequires: tar
|
||||||
|
Provides: bundled(vaptvupt-codec) = 2.65.3
|
||||||
|
Provides: vaptvupt = %{version}-%{release}
|
||||||
|
Obsoletes: vaptvupt < %{version}
|
||||||
|
|
||||||
|
%description
|
||||||
|
ZUPT creates compressed backup archives with optional authenticated
|
||||||
|
password encryption or ML-KEM-768 and X25519 hybrid key encapsulation. The
|
||||||
|
default package is built entirely from the source in the release archive.
|
||||||
|
|
||||||
|
Optional SDK and PQBOX features are disabled because audited development
|
||||||
|
packages are unavailable. No private compiled library is installed.
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%autosetup -p1
|
||||||
|
bash scripts/check-source-only.sh --tree .
|
||||||
|
|
||||||
|
%build
|
||||||
|
%make_build WITH_SDK=0 WITH_PQBOX=0 \
|
||||||
|
CFLAGS="%{optflags} -fPIE" \
|
||||||
|
LDFLAGS="%{?build_ldflags} -Wl,-z,relro,-z,now -pie"
|
||||||
|
|
||||||
|
%check
|
||||||
|
%make_build WITH_SDK=0 WITH_PQBOX=0 \
|
||||||
|
CFLAGS="%{optflags} -fPIE" \
|
||||||
|
LDFLAGS="%{?build_ldflags} -Wl,-z,relro,-z,now -pie" \
|
||||||
|
check
|
||||||
|
|
||||||
|
%install
|
||||||
|
%make_install WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 INSTALL_LICENSES=0 \
|
||||||
|
PREFIX=%{_prefix} \
|
||||||
|
BINDIR=%{_bindir} \
|
||||||
|
MANDIR=%{_mandir}
|
||||||
|
|
||||||
|
%files
|
||||||
|
%license LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE THIRD-PARTY-NOTICES.md
|
||||||
|
%doc README.md CHANGELOG.md SECURITY.md THREAT_MODEL.md
|
||||||
|
%{_bindir}/zupt
|
||||||
|
%{_datadir}/bash-completion/completions/zupt
|
||||||
|
%{_datadir}/zsh/site-functions/_zupt
|
||||||
|
%{_datadir}/fish/vendor_completions.d/zupt.fish
|
||||||
|
%{_mandir}/man1/zupt.1%{?ext_man}
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
|
@ -1,57 +1,59 @@
|
||||||
VaptVupt GUI — portable cross-platform package
|
ZUPT GUI — source-only portable launcher template
|
||||||
==============================================
|
=====================================================
|
||||||
|
|
||||||
The VaptVupt GUI is a single Python file (zupt_gui.py) built on Qt for Python
|
This tracked directory contains three launcher templates and this assembly
|
||||||
(PySide6, or PyQt6 as a fallback). It runs on Windows, macOS, Linux and the
|
guide; it is not a complete bundle by itself. A downstream source-only bundle
|
||||||
BSDs — anywhere Python 3 and a Qt binding are installed. This portable package
|
may add the integrated Python GUI source and artwork listed below, together
|
||||||
contains the GUI plus a launcher for each platform; it drives the `vaptvupt`
|
with the required license/provenance files. It must not contain Python, Qt, a
|
||||||
command-line tool under the hood.
|
precompiled ZUPT command, or a vendored library. Its presence in a release
|
||||||
|
would not be evidence that every target operating system was tested; consult
|
||||||
|
that release's validation matrix.
|
||||||
|
|
||||||
Contents
|
Contents
|
||||||
--------
|
--------
|
||||||
zupt_gui.py The GUI (PySide6 / PyQt6).
|
zupt_gui.py GUI source module (the historical module filename is
|
||||||
vaptvupt-gui.bat Windows launcher.
|
retained internally for source compatibility).
|
||||||
vaptvupt-gui.command macOS launcher (double-clickable in Finder).
|
zupt-gui.bat Windows launcher.
|
||||||
vaptvupt-gui.sh Linux / BSD launcher.
|
zupt-gui.command macOS Finder launcher.
|
||||||
assets/zupt-icon.png Application icon.
|
zupt-gui.sh POSIX shell launcher.
|
||||||
|
assets/zupt-icon.png PNG application artwork.
|
||||||
|
assets/zupt.ico Windows application artwork.
|
||||||
|
LICENSE-AGPL-3.0 Complete current GUI source license text.
|
||||||
|
LICENSE-GUI GUI licensing and historical-license note.
|
||||||
|
ASSET-PROVENANCE.md Artwork purpose, provenance, and license record.
|
||||||
|
CHANGELOG.md Release history and current compatibility notes.
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
1. Python 3.8 or newer.
|
1. Python 3.9 or newer.
|
||||||
Windows: https://python.org (tick "Add python.exe to PATH")
|
2. PySide6 6.5 or newer, or a compatible PyQt6 package.
|
||||||
macOS: python.org, or `brew install python`
|
3. ZUPT 5.2.2, installed as `zupt` on PATH or placed beside the launcher
|
||||||
Linux: your distro's python3 package
|
(`zupt.exe` on Windows). A local command must have been built
|
||||||
FreeBSD: pkg install python311
|
and tested independently; this bundle never downloads one.
|
||||||
OpenBSD: pkg_add python%3
|
|
||||||
2. A Qt binding:
|
|
||||||
pip (any OS): python3 -m pip install PySide6
|
|
||||||
Debian/Ubuntu: sudo apt install python3-pyqt6
|
|
||||||
Fedora/RHEL: sudo dnf install python3-pyqt6
|
|
||||||
FreeBSD: pkg install py311-pyside6
|
|
||||||
OpenBSD: pkg_add py3-pyside6
|
|
||||||
3. The vaptvupt CLI, either:
|
|
||||||
* placed next to the launcher (vaptvupt.exe on Windows, vaptvupt
|
|
||||||
elsewhere) — the launcher auto-detects it via VAPTVUPT_BIN, or
|
|
||||||
* installed on PATH (deb/rpm/AppImage/Homebrew/pkg — see the project
|
|
||||||
release page).
|
|
||||||
|
|
||||||
Running
|
Running
|
||||||
-------
|
-------
|
||||||
Windows: double-click vaptvupt-gui.bat
|
Windows: zupt-gui.bat
|
||||||
macOS: double-click vaptvupt-gui.command
|
macOS: zupt-gui.command
|
||||||
(first run: right-click > Open to bypass Gatekeeper for an
|
POSIX: ./zupt-gui.sh
|
||||||
unsigned script, or `xattr -dr com.apple.quarantine .`)
|
|
||||||
Linux/BSD: ./vaptvupt-gui.sh
|
The launchers set ZUPT_BIN when a local command is present. The GUI then
|
||||||
|
checks `zupt version`, discovers native and optional capabilities, and
|
||||||
|
exposes SDK or PQ-box modes only when the command reports the corresponding
|
||||||
|
system-library integration enabled.
|
||||||
|
|
||||||
Troubleshooting
|
Troubleshooting
|
||||||
---------------
|
---------------
|
||||||
* "requires PySide6 or PyQt6" -> install a Qt binding (requirement 2).
|
* "requires PySide6 or PyQt6": install one Qt binding through your operating
|
||||||
* "vaptvupt not found" -> put the CLI next to the launcher or on PATH.
|
system package manager or another trusted, preconfigured Python source.
|
||||||
* Set VAPTVUPT_DEBUG=1 to print the binary-discovery log to stderr.
|
* "zupt not found": install ZUPT 5.2.2 or place its command beside
|
||||||
|
the launcher.
|
||||||
|
* Set ZUPT_DEBUG=1 to print command-discovery diagnostics to stderr.
|
||||||
|
|
||||||
Fully self-contained native installers (Windows .exe/.msi, macOS .dmg) that
|
The old user-facing command name is not installed by this bundle. The `.zupt`
|
||||||
bundle Python + Qt + the CLI are produced by the project's CI on real Windows
|
archive extension remains unchanged for format compatibility.
|
||||||
and macOS runners — see the release page. This portable package is the
|
|
||||||
dependency-light option that works identically on every platform.
|
|
||||||
|
|
||||||
License: AGPL-3.0-or-later. Project: https://git.securityops.co/cristiancmoises/vaptvupt
|
Current GUI source license: AGPL-3.0-or-later. Published historical revisions
|
||||||
|
include MIT grants for the exact material covered by their notices; see
|
||||||
|
LICENSE-GUI and the 5.2.2 erratum in CHANGELOG.md.
|
||||||
|
Project: https://github.com/cristiancmoises/zupt
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
# VaptVupt GUI launcher for macOS (portable package).
|
|
||||||
# Double-clickable in Finder (.command). Requirements on the target Mac:
|
|
||||||
# * Python 3.8+ (python.org, Homebrew `brew install python`, or Xcode CLT)
|
|
||||||
# * PySide6 or PyQt6: python3 -m pip install PySide6
|
|
||||||
# * The vaptvupt CLI: `vaptvupt` next to this file, or on PATH
|
|
||||||
# (Homebrew: `brew install cristiancmoises/tap/vaptvupt`).
|
|
||||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
[ -x "$HERE/vaptvupt" ] && export VAPTVUPT_BIN="$HERE/vaptvupt"
|
|
||||||
|
|
||||||
PY="$(command -v python3 || command -v python || true)"
|
|
||||||
if [ -z "$PY" ]; then
|
|
||||||
osascript -e 'display alert "VaptVupt GUI" message "Python 3 not found. Install it from python.org or `brew install python`, then run: python3 -m pip install PySide6"' 2>/dev/null
|
|
||||||
echo "Python 3 not found." >&2; exit 1
|
|
||||||
fi
|
|
||||||
exec "$PY" "$HERE/zupt_gui.py" "$@"
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
@echo off
|
@echo off
|
||||||
rem SPDX-License-Identifier: AGPL-3.0-or-later
|
rem SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
rem VaptVupt GUI launcher for Windows (portable package).
|
rem ZUPT GUI launcher for Windows (portable package).
|
||||||
rem
|
rem
|
||||||
rem Requirements on the target machine:
|
rem Requirements on the target machine:
|
||||||
rem * Python 3.8+ (https://python.org — tick "Add python.exe to PATH")
|
rem * Python 3.9+
|
||||||
rem * PySide6 or PyQt6: py -m pip install PySide6
|
rem * PySide6 or PyQt6: py -m pip install PySide6
|
||||||
rem * The vaptvupt CLI: vaptvupt.exe next to this file, or on PATH.
|
rem * The ZUPT CLI: zupt.exe next to this file, or on PATH.
|
||||||
rem
|
rem
|
||||||
rem If vaptvupt.exe sits beside this launcher we pin it via VAPTVUPT_BIN so the
|
rem If zupt.exe sits beside this launcher we pin it via ZUPT_BIN so the
|
||||||
rem GUI drives the bundled CLI rather than any other copy on PATH.
|
rem GUI drives the bundled CLI rather than any other copy on PATH.
|
||||||
setlocal
|
setlocal
|
||||||
set "HERE=%~dp0"
|
set "HERE=%~dp0"
|
||||||
if exist "%HERE%vaptvupt.exe" set "VAPTVUPT_BIN=%HERE%vaptvupt.exe"
|
if exist "%HERE%zupt.exe" set "ZUPT_BIN=%HERE%zupt.exe"
|
||||||
|
|
||||||
rem Prefer the py launcher, fall back to python on PATH.
|
rem Prefer the py launcher, fall back to python on PATH.
|
||||||
where py >nul 2>nul
|
where py >nul 2>nul
|
||||||
|
|
@ -23,9 +23,8 @@ if %ERRORLEVEL%==0 (
|
||||||
set "RC=%ERRORLEVEL%"
|
set "RC=%ERRORLEVEL%"
|
||||||
if not "%RC%"=="0" (
|
if not "%RC%"=="0" (
|
||||||
echo.
|
echo.
|
||||||
echo vaptvupt-gui exited with code %RC%.
|
echo zupt-gui exited with code %RC%.
|
||||||
echo If you saw an import error, install the Qt binding: py -m pip install PySide6
|
echo If you saw an import error, install the Qt binding: py -m pip install PySide6
|
||||||
echo If the CLI was not found, put vaptvupt.exe next to this launcher or on PATH.
|
echo If the CLI was not found, put zupt.exe next to this launcher or on PATH.
|
||||||
pause
|
|
||||||
)
|
)
|
||||||
endlocal
|
endlocal & exit /b %RC%
|
||||||
17
packaging/portable/zupt-gui.command
Executable file
17
packaging/portable/zupt-gui.command
Executable file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
# ZUPT GUI launcher for macOS (portable package).
|
||||||
|
# Double-clickable in Finder (.command). Requirements on the target Mac:
|
||||||
|
# * Python 3.9+
|
||||||
|
# * PySide6 or PyQt6: python3 -m pip install PySide6
|
||||||
|
# * The ZUPT CLI: `zupt` next to this file, or on PATH
|
||||||
|
# (Homebrew: `brew install cristiancmoises/tap/zupt`).
|
||||||
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
[ -x "$HERE/zupt" ] && export ZUPT_BIN="$HERE/zupt"
|
||||||
|
|
||||||
|
PY="$(command -v python3 || command -v python || true)"
|
||||||
|
if [ -z "$PY" ]; then
|
||||||
|
osascript -e 'display alert "ZUPT GUI" message "Python 3.9 or newer was not found. Install Python and a trusted PySide6 or PyQt6 package."' 2>/dev/null
|
||||||
|
echo "Python 3 not found." >&2; exit 1
|
||||||
|
fi
|
||||||
|
exec "$PY" "$HERE/zupt_gui.py" "$@"
|
||||||
10
packaging/portable/vaptvupt-gui.sh → packaging/portable/zupt-gui.sh
Normal file → Executable file
10
packaging/portable/vaptvupt-gui.sh → packaging/portable/zupt-gui.sh
Normal file → Executable file
|
|
@ -1,21 +1,21 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# VaptVupt GUI launcher for Linux and the BSDs (portable package).
|
# ZUPT GUI launcher for Linux and the BSDs (portable package).
|
||||||
# Requirements on the target system:
|
# Requirements on the target system:
|
||||||
# * Python 3.8+
|
# * Python 3.9+
|
||||||
# * PySide6 or PyQt6
|
# * PySide6 or PyQt6
|
||||||
# Debian/Ubuntu: sudo apt install python3-pyqt6
|
# Debian/Ubuntu: sudo apt install python3-pyqt6
|
||||||
# Fedora/RHEL: sudo dnf install python3-pyqt6
|
# Fedora/RHEL: sudo dnf install python3-pyqt6
|
||||||
# FreeBSD: pkg install py311-pyside6 (or py311-qt6-pyqt)
|
# FreeBSD: pkg install py311-pyside6 (or py311-qt6-pyqt)
|
||||||
# OpenBSD: pkg_add py3-pyside6
|
# OpenBSD: pkg_add py3-pyside6
|
||||||
# any OS via pip: python3 -m pip install PySide6
|
# any OS via pip: python3 -m pip install PySide6
|
||||||
# * The vaptvupt CLI: `vaptvupt` next to this file, or on PATH.
|
# * The ZUPT CLI: `zupt` next to this file, or on PATH.
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
[ -x "$HERE/vaptvupt" ] && export VAPTVUPT_BIN="$HERE/vaptvupt"
|
[ -x "$HERE/zupt" ] && export ZUPT_BIN="$HERE/zupt"
|
||||||
|
|
||||||
PY="$(command -v python3 || command -v python || true)"
|
PY="$(command -v python3 || command -v python || true)"
|
||||||
if [ -z "$PY" ]; then
|
if [ -z "$PY" ]; then
|
||||||
echo "vaptvupt-gui: Python 3 not found on PATH." >&2
|
echo "zupt-gui: Python 3 not found on PATH." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
exec "$PY" "$HERE/zupt_gui.py" "$@"
|
exec "$PY" "$HERE/zupt_gui.py" "$@"
|
||||||
|
|
@ -1,72 +1,70 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# Fedora / RHEL / CentOS RPM spec for vaptvupt.
|
# Fedora / RHEL / CentOS RPM spec for zupt.
|
||||||
#
|
#
|
||||||
# Build with:
|
# Build with:
|
||||||
# spectool -g vaptvupt.spec # fetches the upstream tarball
|
# spectool -g zupt.spec # fetches the upstream tarball
|
||||||
# rpmbuild -ba vaptvupt.spec # builds source + binary RPMs
|
# rpmbuild -ba zupt.spec # builds source + binary RPMs
|
||||||
#
|
#
|
||||||
# To bring a release into production:
|
# To bring a release into production:
|
||||||
# 1. Run `make dist` upstream → /tmp/vaptvupt-VERSION.tar.gz (reproducible).
|
# 1. Run `make dist` upstream → /tmp/zupt-VERSION.tar.gz (reproducible).
|
||||||
# 2. Upload to a stable release URL (git.securityops.co releases).
|
# 2. Upload to the canonical GitHub release.
|
||||||
# 3. Update %{version} below.
|
# 3. Update %{version} below.
|
||||||
# 4. Run `sha256sum /tmp/vaptvupt-VERSION.tar.gz` and update Source0
|
# 4. Run `sha256sum /tmp/zupt-VERSION.tar.gz` and update Source0
|
||||||
# checksum (handled by spectool when configured) or pin via
|
# checksum (handled by spectool when configured) or pin via
|
||||||
# sha256sum in a separate manifest if your distro requires it.
|
# sha256sum in a separate manifest if your distro requires it.
|
||||||
# 5. rpmbuild --define '_topdir ~/rpmbuild' -ba zupt.spec
|
# 5. rpmbuild --define '_topdir /path/to/rpmbuild' -ba zupt.spec
|
||||||
#
|
#
|
||||||
# This spec is written for Fedora 38+ and EPEL 9+; it should also work
|
# This is an upstream Fedora-family recipe. A target is supported only after
|
||||||
# on RHEL 8 (with EPEL) by adjusting BuildRequires if Python 3.8+ isn't
|
# that exact distribution release and architecture have built and passed the
|
||||||
# in the base.
|
# installed smoke test.
|
||||||
|
|
||||||
Name: vaptvupt
|
Name: zupt
|
||||||
Version: 5.2.1
|
Version: 5.2.2
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: Post-quantum backup compression utility (AES-256 + ML-KEM-768 + Argon2id, formerly Zupt)
|
Summary: Backup compression with authenticated and post-quantum encryption
|
||||||
|
|
||||||
License: AGPL-3.0-or-later AND GPL-3.0-or-later
|
License: AGPL-3.0-or-later AND GPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND CC0-1.0
|
||||||
URL: https://git.securityops.co/cristiancmoises/vaptvupt
|
URL: https://github.com/cristiancmoises/zupt
|
||||||
Source0: %{url}/releases/download/v%{version}/%{name}-%{version}.tar.gz
|
Source0: %{url}/releases/download/v%{version}/%{name}-%{version}.tar.gz
|
||||||
|
|
||||||
# v3.0.0: legacy `zupt` package is superseded. Renaming was forced
|
|
||||||
# by a prior INPI Brasil trademark registration on "Zupt". The
|
|
||||||
# archive extension (.zupt), wire format, magic bytes, and C ABI
|
|
||||||
# are unchanged.
|
|
||||||
Provides: zupt = %{version}-%{release}
|
|
||||||
Obsoletes: zupt < 3.0.0
|
|
||||||
Conflicts: zupt < 3.0.0
|
|
||||||
|
|
||||||
BuildRequires: gcc
|
BuildRequires: gcc
|
||||||
|
BuildRequires: git-core
|
||||||
BuildRequires: make
|
BuildRequires: make
|
||||||
BuildRequires: glibc-devel
|
BuildRequires: glibc-devel
|
||||||
BuildRequires: python3 >= 3.8
|
BuildRequires: python3 >= 3.8
|
||||||
|
BuildRequires: bash
|
||||||
|
BuildRequires: coreutils
|
||||||
|
BuildRequires: diffutils
|
||||||
|
BuildRequires: file
|
||||||
|
BuildRequires: findutils
|
||||||
|
BuildRequires: gawk
|
||||||
|
BuildRequires: grep
|
||||||
|
BuildRequires: gzip
|
||||||
|
BuildRequires: sed
|
||||||
|
BuildRequires: tar
|
||||||
# python3 is only needed for the regression-test harness (byte sweeps,
|
# python3 is only needed for the regression-test harness (byte sweeps,
|
||||||
# tamper injection). The shipped binary has no Python dependency.
|
# tamper injection). The shipped binary has no Python dependency.
|
||||||
|
|
||||||
Requires: glibc
|
Provides: bundled(vaptvupt-codec) = 2.65.3
|
||||||
|
|
||||||
%description
|
%description
|
||||||
Zupt is a pure-C11 backup compression utility featuring:
|
ZUPT is a pure-C11 backup compression utility featuring:
|
||||||
|
|
||||||
* Post-quantum hybrid encryption (ML-KEM-768 + X25519, FIPS 203,
|
* Post-quantum hybrid encryption (ML-KEM-768 + X25519) and full
|
||||||
validated byte-for-byte against OpenSSL's ML-KEM-768) and full
|
ML-KEM-768 mode (--pq-only)
|
||||||
pure ML-KEM-768 (--pq-only)
|
|
||||||
* AES-256-CTR + HMAC-SHA256 authenticated encryption (Encrypt-then-MAC)
|
* AES-256-CTR + HMAC-SHA256 authenticated encryption (Encrypt-then-MAC)
|
||||||
* PBKDF2-SHA256 password key derivation (Argon2id in WITH_SDK=1 builds)
|
* PBKDF2-SHA256 password key derivation (Argon2id in WITH_SDK=1 builds)
|
||||||
* Multi-threaded compression with the VaptVupt LZ + ANS codec
|
* Multi-threaded compression with the VaptVupt LZ + ANS codec
|
||||||
* Full-disk backup and restore with sparse-region detection
|
* Full-disk backup and restore with sparse-region detection
|
||||||
* End-to-end byte-level tamper detection on encrypted archives
|
* Authenticated encrypted-archive metadata and per-block integrity checks
|
||||||
(0 silent-accept positions in the v1.6 exhaustive byte sweep)
|
* Portable C implementations with optional source-built assembly paths
|
||||||
* Constant-time cryptographic primitives verified with Jasmin
|
|
||||||
* NIST/RFC test vectors for SHA-256, SHA-3, ML-KEM-768, AES-256-CTR,
|
* NIST/RFC test vectors for SHA-256, SHA-3, ML-KEM-768, AES-256-CTR,
|
||||||
HMAC-SHA256, X25519, PBKDF2, Argon2id
|
HMAC-SHA256, X25519 and PBKDF2
|
||||||
|
|
||||||
The archive format includes an integrity trailer that authenticates
|
Encrypted archives include an integrity trailer that authenticates the header
|
||||||
the header and footer, per-block HMAC with bound frame-preface AAD,
|
and footer, per-block HMAC with bound frame-preface AAD, and optional encrypted
|
||||||
and optional encrypted comments.
|
comments. Plain archives use non-cryptographic checksums.
|
||||||
|
|
||||||
%global debug_package %{nil}
|
|
||||||
# Single source RPM, no -debuginfo split for the initial release.
|
|
||||||
|
|
||||||
%prep
|
%prep
|
||||||
%autosetup -n %{name}-%{version}
|
%autosetup -n %{name}-%{version}
|
||||||
|
|
@ -74,41 +72,41 @@ and optional encrypted comments.
|
||||||
%build
|
%build
|
||||||
# Source-only build (WITH_SDK=0): no vendored libraries, no external crypto
|
# Source-only build (WITH_SDK=0): no vendored libraries, no external crypto
|
||||||
# dependency. Fedora's default optflags plus the project's warning set.
|
# dependency. Fedora's default optflags plus the project's warning set.
|
||||||
%make_build WITH_SDK=0 \
|
%make_build V=1 WITH_SDK=0 WITH_PQBOX=0 \
|
||||||
CFLAGS="%{optflags} -fPIE -Wall -Wextra -std=c11 -Iinclude -Isrc" \
|
CFLAGS="%{optflags}" \
|
||||||
LDFLAGS="%{?build_ldflags} -pie" \
|
LDFLAGS="%{?build_ldflags}"
|
||||||
LDLIBS="-lm -lpthread"
|
|
||||||
|
|
||||||
%check
|
%check
|
||||||
# Distro-safe regression subset: F-06 HMAC trials, F-08 top-MAC sweep,
|
# Distro-safe quick, path-traversal, integrity, codec, HMAC and NIST/RFC
|
||||||
# F-09 byte sweep, F-10..F-12 regressions, the dedup-nonce regression,
|
# checks. Full, optional-integration and dist-reproducibility suites remain
|
||||||
# and NIST/RFC vectors. Skips threaded/dist-reproducibility tests that
|
# release gates outside the package build.
|
||||||
# are sensitive to the build host.
|
%make_build V=1 WITH_SDK=0 WITH_PQBOX=0 \
|
||||||
%make_build WITH_SDK=0 \
|
CFLAGS="%{optflags}" \
|
||||||
CFLAGS="%{optflags} -fPIE -Wall -Wextra -std=c11 -Iinclude -Isrc" \
|
LDFLAGS="%{?build_ldflags}" \
|
||||||
LDFLAGS="%{?build_ldflags} -pie" \
|
|
||||||
LDLIBS="-lm -lpthread" \
|
|
||||||
check
|
check
|
||||||
|
|
||||||
%install
|
%install
|
||||||
%make_install WITH_SDK=0 DESTDIR=%{buildroot} PREFIX=/usr
|
%make_install WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 INSTALL_LICENSES=0 \
|
||||||
|
PREFIX=%{_prefix} BINDIR=%{_bindir} MANDIR=%{_mandir}
|
||||||
|
|
||||||
%files
|
%files
|
||||||
%license LICENSE
|
%license LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 NOTICE THIRD-PARTY-NOTICES.md
|
||||||
%doc README.md SECURITY.md CHANGELOG.md
|
%doc README.md SECURITY.md THREAT_MODEL.md CHANGELOG.md
|
||||||
%{_bindir}/%{name}
|
%{_bindir}/%{name}
|
||||||
%{_bindir}/zupt
|
|
||||||
%{_datadir}/bash-completion/completions/%{name}
|
%{_datadir}/bash-completion/completions/%{name}
|
||||||
%{_datadir}/bash-completion/completions/zupt
|
|
||||||
%{_datadir}/zsh/site-functions/_%{name}
|
%{_datadir}/zsh/site-functions/_%{name}
|
||||||
%{_datadir}/zsh/site-functions/_zupt
|
|
||||||
%{_datadir}/fish/vendor_completions.d/%{name}.fish
|
%{_datadir}/fish/vendor_completions.d/%{name}.fish
|
||||||
%if 0%{?_mandir:1}
|
%if 0%{?_mandir:1}
|
||||||
%{_mandir}/man1/%{name}.1*
|
%{_mandir}/man1/%{name}.1*
|
||||||
%{_mandir}/man1/zupt.1*
|
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.2.2-1
|
||||||
|
- Source-only release; optional SDK/PQBOX integrations use system development
|
||||||
|
packages only and are disabled for this package.
|
||||||
|
- Preserve distribution flags and debuginfo, remove RPATH/vendor-library
|
||||||
|
fallbacks, run the real upstream check target, and restore the zupt command.
|
||||||
|
|
||||||
* Sat Jul 11 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.1.0-1
|
* Sat Jul 11 2026 Cristian Cezar Moisés <sac@securityops.co> - 5.1.0-1
|
||||||
- Codec 2.65.0; large compression-ratio gains (auto format_v2 + level-scaled
|
- Codec 2.65.0; large compression-ratio gains (auto format_v2 + level-scaled
|
||||||
block window); --dedup keeps a small block; GUI compress-hang and
|
block window); --dedup keeps a small block; GUI compress-hang and
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
; SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
; Inno Setup script for the VaptVupt GUI Windows installer.
|
|
||||||
;
|
|
||||||
; Compiled by the cross-platform CI (.github/workflows/cross-platform.yml) with:
|
|
||||||
; ISCC.exe /DAppVersion=<version> packaging/windows/vaptvupt-gui.iss
|
|
||||||
; after PyInstaller has produced dist\vaptvupt-gui.exe (a onefile bundle that
|
|
||||||
; already contains Python, PySide6 and vaptvupt.exe). Requires Inno Setup 6+.
|
|
||||||
;
|
|
||||||
; To build locally on Windows: install Inno Setup, then run the same ISCC line
|
|
||||||
; from the repo root (with dist\vaptvupt-gui.exe present).
|
|
||||||
|
|
||||||
#ifndef AppVersion
|
|
||||||
#define AppVersion "0.0.0"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
[Setup]
|
|
||||||
AppName=VaptVupt
|
|
||||||
AppVersion={#AppVersion}
|
|
||||||
AppPublisher=Cristian Cezar Moises
|
|
||||||
AppPublisherURL=https://git.securityops.co/cristiancmoises/vaptvupt
|
|
||||||
DefaultDirName={autopf}\VaptVupt
|
|
||||||
DefaultGroupName=VaptVupt
|
|
||||||
UninstallDisplayIcon={app}\vaptvupt-gui.exe
|
|
||||||
OutputDir=packaging\windows\Output
|
|
||||||
OutputBaseFilename=VaptVupt-Setup-{#AppVersion}
|
|
||||||
Compression=lzma2
|
|
||||||
SolidCompression=yes
|
|
||||||
ArchitecturesAllowed=x64compatible
|
|
||||||
ArchitecturesInstallIn64BitMode=x64compatible
|
|
||||||
WizardStyle=modern
|
|
||||||
LicenseFile=LICENSE
|
|
||||||
|
|
||||||
[Languages]
|
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
|
||||||
|
|
||||||
[Files]
|
|
||||||
; PyInstaller onefile bundle (Python + PySide6 + the GUI + vaptvupt.exe).
|
|
||||||
Source: "dist\vaptvupt-gui.exe"; DestDir: "{app}"; Flags: ignoreversion
|
|
||||||
; Ship the raw CLI too so it can be added to PATH and used from a terminal.
|
|
||||||
Source: "vaptvupt.exe"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist
|
|
||||||
Source: "README.md"; DestDir: "{app}"; Flags: ignoreversion isreadme
|
|
||||||
Source: "CHANGELOG.md"; DestDir: "{app}"; Flags: ignoreversion
|
|
||||||
|
|
||||||
[Icons]
|
|
||||||
Name: "{group}\VaptVupt"; Filename: "{app}\vaptvupt-gui.exe"
|
|
||||||
Name: "{group}\Uninstall VaptVupt"; Filename: "{uninstallexe}"
|
|
||||||
Name: "{autodesktop}\VaptVupt"; Filename: "{app}\vaptvupt-gui.exe"; Tasks: desktopicon
|
|
||||||
|
|
||||||
[Tasks]
|
|
||||||
Name: "desktopicon"; Description: "Create a desktop shortcut"; GroupDescription: "Additional icons:"
|
|
||||||
Name: "addtopath"; Description: "Add the vaptvupt CLI to PATH (current user)"; GroupDescription: "Command line:"
|
|
||||||
|
|
||||||
[Registry]
|
|
||||||
; Optionally add the install dir to the user PATH (for the vaptvupt.exe CLI).
|
|
||||||
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; \
|
|
||||||
ValueData: "{olddata};{app}"; Tasks: addtopath; Check: NeedsAddPath('{app}')
|
|
||||||
|
|
||||||
[Run]
|
|
||||||
Filename: "{app}\vaptvupt-gui.exe"; Description: "Launch VaptVupt"; \
|
|
||||||
Flags: nowait postinstall skipifsilent
|
|
||||||
|
|
||||||
[Code]
|
|
||||||
function NeedsAddPath(Param: string): Boolean;
|
|
||||||
var
|
|
||||||
OrigPath: string;
|
|
||||||
begin
|
|
||||||
if not RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', OrigPath) then
|
|
||||||
begin
|
|
||||||
Result := True;
|
|
||||||
exit;
|
|
||||||
end;
|
|
||||||
Result := Pos(';' + ExpandConstant(Param) + ';', ';' + OrigPath + ';') = 0;
|
|
||||||
end;
|
|
||||||
98
packaging/windows/zupt-gui.iss
Normal file
98
packaging/windows/zupt-gui.iss
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
; SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
; Inno Setup 6 recipe for target-built ZUPT Windows artifacts.
|
||||||
|
;
|
||||||
|
; All paths are mandatory command-line definitions. This prevents the recipe
|
||||||
|
; from silently picking up a stale or placeholder executable from the tree.
|
||||||
|
|
||||||
|
#ifndef AppVersion
|
||||||
|
#error AppVersion must be defined
|
||||||
|
#endif
|
||||||
|
#ifndef GuiExecutable
|
||||||
|
#error GuiExecutable must name a tested PyInstaller GUI executable
|
||||||
|
#endif
|
||||||
|
#ifndef CliExecutable
|
||||||
|
#error CliExecutable must name a tested source-built zupt.exe
|
||||||
|
#endif
|
||||||
|
#ifndef BuildOutputDir
|
||||||
|
#error BuildOutputDir must be an external output directory
|
||||||
|
#endif
|
||||||
|
#ifndef RuntimeNoticesDir
|
||||||
|
#error RuntimeNoticesDir must contain notices for the exact bundled GUI runtime
|
||||||
|
#endif
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
AppId={{59AD35E4-1860-445D-8E89-4563DB9ED4E2}
|
||||||
|
AppName=ZUPT
|
||||||
|
AppVersion={#AppVersion}
|
||||||
|
AppPublisher=Cristian Cezar Moises
|
||||||
|
AppPublisherURL=https://github.com/cristiancmoises/zupt
|
||||||
|
AppSupportURL=https://github.com/cristiancmoises/zupt/issues
|
||||||
|
DefaultDirName={autopf}\ZUPT
|
||||||
|
DefaultGroupName=ZUPT
|
||||||
|
UninstallDisplayIcon={app}\zupt-gui.exe
|
||||||
|
OutputDir={#BuildOutputDir}
|
||||||
|
OutputBaseFilename=ZUPT-Setup-{#AppVersion}
|
||||||
|
Compression=lzma2
|
||||||
|
SolidCompression=yes
|
||||||
|
ArchitecturesAllowed=x64compatible
|
||||||
|
ArchitecturesInstallIn64BitMode=x64compatible
|
||||||
|
WizardStyle=modern
|
||||||
|
LicenseFile=..\..\LICENSE
|
||||||
|
ChangesAssociations=yes
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "{#GuiExecutable}"; DestDir: "{app}"; DestName: "zupt-gui.exe"; Flags: ignoreversion
|
||||||
|
Source: "{#CliExecutable}"; DestDir: "{app}"; DestName: "zupt.exe"; Flags: ignoreversion
|
||||||
|
Source: "..\..\LICENSE"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\..\LICENSE-AGPL-3.0"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\..\LICENSE-GPL-3.0"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\..\LICENSE-BSD-2-Clause"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\..\LICENSE-BSD-3-Clause"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\..\LICENSE-CC0-1.0"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\..\NOTICE"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\..\THIRD-PARTY-NOTICES.md"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\..\gui\LICENSE-GUI"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\..\gui\assets\README.md"; DestDir: "{app}"; DestName: "GUI-ASSET-PROVENANCE.md"; Flags: ignoreversion
|
||||||
|
Source: "{#RuntimeNoticesDir}\*"; DestDir: "{app}\third-party-runtime-notices"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
Source: "..\..\README.md"; DestDir: "{app}"; Flags: ignoreversion isreadme
|
||||||
|
Source: "..\..\CHANGELOG.md"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\ZUPT GUI"; Filename: "{app}\zupt-gui.exe"
|
||||||
|
Name: "{group}\ZUPT command prompt"; Filename: "{cmd}"; Parameters: "/K cd /d ""{app}"""
|
||||||
|
Name: "{group}\Uninstall ZUPT"; Filename: "{uninstallexe}"
|
||||||
|
Name: "{autodesktop}\ZUPT GUI"; Filename: "{app}\zupt-gui.exe"; Tasks: desktopicon
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "Create a desktop shortcut"; GroupDescription: "Additional icons:"
|
||||||
|
Name: "addtopath"; Description: "Add the ZUPT command to PATH for this user"; GroupDescription: "Command line:"
|
||||||
|
|
||||||
|
[Registry]
|
||||||
|
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; \
|
||||||
|
ValueData: "{olddata};{app}"; Tasks: addtopath; Check: NeedsAddPath('{app}')
|
||||||
|
Root: HKCU; Subkey: "Software\Classes\.zupt"; ValueType: string; ValueName: ""; \
|
||||||
|
ValueData: "ZUPT.Archive"; Flags: uninsdeletevalue
|
||||||
|
Root: HKCU; Subkey: "Software\Classes\ZUPT.Archive"; ValueType: string; \
|
||||||
|
ValueName: ""; ValueData: "ZUPT archive"; Flags: uninsdeletekey
|
||||||
|
Root: HKCU; Subkey: "Software\Classes\ZUPT.Archive\shell\open\command"; \
|
||||||
|
ValueType: string; ValueName: ""; ValueData: """{app}\zupt-gui.exe"" --extract ""%1"""
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\zupt-gui.exe"; Description: "Launch ZUPT GUI"; \
|
||||||
|
Flags: nowait postinstall skipifsilent
|
||||||
|
|
||||||
|
[Code]
|
||||||
|
function NeedsAddPath(Param: string): Boolean;
|
||||||
|
var
|
||||||
|
OrigPath: string;
|
||||||
|
begin
|
||||||
|
if not RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', OrigPath) then
|
||||||
|
begin
|
||||||
|
Result := True;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
Result := Pos(';' + ExpandConstant(Param) + ';', ';' + OrigPath + ';') = 0;
|
||||||
|
end;
|
||||||
|
|
@ -1,312 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
|
||||||
# ╔════════════════════════════════════════════════════════════════════╗
|
|
||||||
# ║ ZUPT 2.2.3 + ZUPT-GUI 1.1.1 — UNIVERSAL LINUX INSTALLER ║
|
|
||||||
# ║ ║
|
|
||||||
# ║ One script, all distributions. Self-extracting. No internet ║
|
|
||||||
# ║ needed for the package install (only for Qt6 dependency). ║
|
|
||||||
# ║ ║
|
|
||||||
# ║ Usage: sudo bash zupt-installer.sh ║
|
|
||||||
# ║ Or: sudo bash zupt-installer.sh --gui-only ║
|
|
||||||
# ║ Or: sudo bash zupt-installer.sh --cli-only ║
|
|
||||||
# ║ Or: sudo bash zupt-installer.sh --appimage ║
|
|
||||||
# ║ Or: sudo bash zupt-installer.sh --uninstall ║
|
|
||||||
# ╚════════════════════════════════════════════════════════════════════╝
|
|
||||||
set -e
|
|
||||||
|
|
||||||
VERSION="2.2.3"
|
|
||||||
GUI_VERSION="1.1.1"
|
|
||||||
EXTRACT_DIR=""
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
[ -n "$EXTRACT_DIR" ] && [ -d "$EXTRACT_DIR" ] && rm -rf "$EXTRACT_DIR"
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
# ── Color output (if terminal supports) ─────────────────────────────
|
|
||||||
if [ -t 1 ]; then
|
|
||||||
BOLD='\033[1m'; CYAN='\033[36m'; GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'; RESET='\033[0m'
|
|
||||||
else
|
|
||||||
BOLD=''; CYAN=''; GREEN=''; YELLOW=''; RED=''; RESET=''
|
|
||||||
fi
|
|
||||||
|
|
||||||
step() { echo -e "${CYAN}${BOLD}═══ $* ═══${RESET}"; }
|
|
||||||
ok() { echo -e "${GREEN}✓${RESET} $*"; }
|
|
||||||
warn() { echo -e "${YELLOW}⚠${RESET} $*"; }
|
|
||||||
err() { echo -e "${RED}✗${RESET} $*" >&2; }
|
|
||||||
die() { err "$*"; exit 1; }
|
|
||||||
|
|
||||||
# ── Parse arguments ─────────────────────────────────────────────────
|
|
||||||
MODE="full"
|
|
||||||
case "${1:-}" in
|
|
||||||
--cli-only) MODE="cli" ;;
|
|
||||||
--gui-only) MODE="gui" ;;
|
|
||||||
--appimage) MODE="appimage" ;;
|
|
||||||
--uninstall) MODE="uninstall" ;;
|
|
||||||
--help|-h)
|
|
||||||
sed -n '2,15p' "$0" | sed 's/^# //'
|
|
||||||
exit 0 ;;
|
|
||||||
"") MODE="full" ;;
|
|
||||||
*) die "Unknown option: $1. Use --help for options." ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# ── Root check (except for AppImage) ────────────────────────────────
|
|
||||||
if [ "$MODE" != "appimage" ] && [ "$EUID" -ne 0 ]; then
|
|
||||||
die "Run with sudo: sudo bash $0 ${1:-}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Distro detection ────────────────────────────────────────────────
|
|
||||||
detect_distro() {
|
|
||||||
if [ -f /etc/os-release ]; then
|
|
||||||
# Use subshell to prevent /etc/os-release VERSION from clobbering ours
|
|
||||||
DISTRO=$(. /etc/os-release; echo "${ID:-unknown}")
|
|
||||||
DISTRO_LIKE=$(. /etc/os-release; echo "${ID_LIKE:-}")
|
|
||||||
DISTRO_NAME=$(. /etc/os-release; echo "${PRETTY_NAME:-$DISTRO}")
|
|
||||||
else
|
|
||||||
DISTRO="unknown"; DISTRO_LIKE=""; DISTRO_NAME="Unknown Linux"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
detect_distro
|
|
||||||
|
|
||||||
# Categorize
|
|
||||||
DEB_BASED=0; RPM_BASED=0; ARCH_BASED=0; ALPINE=0
|
|
||||||
case "$DISTRO" in
|
|
||||||
debian|ubuntu|linuxmint|pop|elementary|kali|raspbian|deepin|zorin) DEB_BASED=1 ;;
|
|
||||||
fedora|rhel|centos|rocky|almalinux|ol) RPM_BASED=1 ;;
|
|
||||||
opensuse*|suse|sles) RPM_BASED=1 ;;
|
|
||||||
arch|manjaro|endeavouros|garuda|artix) ARCH_BASED=1 ;;
|
|
||||||
alpine) ALPINE=1 ;;
|
|
||||||
*)
|
|
||||||
case "$DISTRO_LIKE" in
|
|
||||||
*debian*|*ubuntu*) DEB_BASED=1 ;;
|
|
||||||
*fedora*|*rhel*|*suse*) RPM_BASED=1 ;;
|
|
||||||
*arch*) ARCH_BASED=1 ;;
|
|
||||||
esac ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# ── Self-extract embedded payload ───────────────────────────────────
|
|
||||||
extract_payload() {
|
|
||||||
EXTRACT_DIR=$(mktemp -d -t zupt-installer.XXXXXX)
|
|
||||||
# Find the line number where the payload starts (marker: __PAYLOAD_BELOW__)
|
|
||||||
local marker_line
|
|
||||||
marker_line=$(grep -an '^__PAYLOAD_BELOW__$' "$0" | head -1 | cut -d: -f1)
|
|
||||||
[ -z "$marker_line" ] && die "Installer is corrupt — no payload marker."
|
|
||||||
# Skip past marker line, decode base64 → tar
|
|
||||||
tail -n +$((marker_line + 1)) "$0" | base64 -d | tar -xzC "$EXTRACT_DIR"
|
|
||||||
[ -f "$EXTRACT_DIR/zupt_${VERSION}_amd64.deb" ] || die "Payload extraction failed."
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Install Qt6 binding (needs network) ─────────────────────────────
|
|
||||||
install_qt6() {
|
|
||||||
if python3 -c 'import PyQt6.QtWidgets' 2>/dev/null \
|
|
||||||
|| python3 -c 'import PySide6.QtWidgets' 2>/dev/null; then
|
|
||||||
ok "Qt6 binding already installed"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
step "Installing Python 3 + Qt6 binding"
|
|
||||||
if [ $DEB_BASED -eq 1 ]; then
|
|
||||||
apt-get update -qq || warn "apt-get update failed (network?); continuing anyway"
|
|
||||||
apt-get install -y python3 python3-pyqt6 \
|
|
||||||
|| apt-get install -y python3 python3-pyside6 \
|
|
||||||
|| warn "Could not install Qt6 binding via apt"
|
|
||||||
elif [ $RPM_BASED -eq 1 ]; then
|
|
||||||
case "$DISTRO" in
|
|
||||||
opensuse*|suse|sles)
|
|
||||||
zypper --non-interactive install python3 python3-pyqt6 \
|
|
||||||
|| zypper --non-interactive install python3 python3-PyQt6 \
|
|
||||||
|| zypper --non-interactive install python3 python3-pyside6 ;;
|
|
||||||
*)
|
|
||||||
if command -v dnf >/dev/null; then
|
|
||||||
dnf install -y python3 python3-pyqt6 \
|
|
||||||
|| dnf install -y python3 python3-pyside6
|
|
||||||
else
|
|
||||||
yum install -y python3 python3-pyqt6 \
|
|
||||||
|| yum install -y python3 python3-pyside6
|
|
||||||
fi ;;
|
|
||||||
esac
|
|
||||||
elif [ $ARCH_BASED -eq 1 ]; then
|
|
||||||
pacman -Sy --noconfirm python python-pyqt6 \
|
|
||||||
|| pacman -Sy --noconfirm python python-pyside6
|
|
||||||
elif [ $ALPINE -eq 1 ]; then
|
|
||||||
apk add python3 py3-pyqt6 || apk add python3 py3-pyside6
|
|
||||||
else
|
|
||||||
warn "Unknown distribution. Trying pip fallback..."
|
|
||||||
if command -v pip3 >/dev/null; then
|
|
||||||
pip3 install --break-system-packages PySide6 2>/dev/null \
|
|
||||||
|| pip3 install --user PySide6
|
|
||||||
else
|
|
||||||
warn "No pip3. Install python3-pyqt6 manually."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if python3 -c 'import PyQt6.QtWidgets' 2>/dev/null \
|
|
||||||
|| python3 -c 'import PySide6.QtWidgets' 2>/dev/null; then
|
|
||||||
ok "Qt6 binding installed"
|
|
||||||
else
|
|
||||||
warn "Qt6 binding install failed. The CLI will still work; the GUI won't."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Install zupt CLI ────────────────────────────────────────────────
|
|
||||||
install_cli() {
|
|
||||||
step "Installing zupt CLI ${VERSION}"
|
|
||||||
if [ $DEB_BASED -eq 1 ]; then
|
|
||||||
dpkg -i "$EXTRACT_DIR/zupt_${VERSION}_amd64.deb" 2>&1 \
|
|
||||||
| grep -v '^Selecting\|^Preparing\|^Unpacking\|^Setting up\|^Processing' || true
|
|
||||||
# Resolve any missing libs from apt
|
|
||||||
apt-get -f install -y 2>/dev/null || true
|
|
||||||
ok "zupt CLI installed: $(zupt version 2>&1 | head -1)"
|
|
||||||
elif [ $RPM_BASED -eq 1 ]; then
|
|
||||||
local rpmtar="$EXTRACT_DIR/zupt-${VERSION}.srpm.tar.gz"
|
|
||||||
if command -v rpmbuild >/dev/null; then
|
|
||||||
local rpmroot=$(mktemp -d)
|
|
||||||
tar -xzC "$rpmroot" -f "$rpmtar"
|
|
||||||
rpmbuild --define "_topdir $rpmroot" -bb "$rpmroot/SPECS/zupt.spec"
|
|
||||||
rpm -Uvh --force "$rpmroot"/RPMS/x86_64/zupt-*.rpm
|
|
||||||
rm -rf "$rpmroot"
|
|
||||||
else
|
|
||||||
# rpmbuild not available — fall back to tarball
|
|
||||||
warn "rpmbuild missing — using portable binary install"
|
|
||||||
local appdir="$EXTRACT_DIR/zupt-${VERSION}-x86_64.AppDir.tar.gz"
|
|
||||||
mkdir -p /opt
|
|
||||||
tar -xzC /opt -f "$appdir"
|
|
||||||
ln -sf "/opt/zupt-${VERSION}-x86_64.AppDir/AppRun" /usr/local/bin/zupt
|
|
||||||
ok "zupt CLI installed (portable mode)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# Universal fallback: portable AppDir tarball
|
|
||||||
warn "No native package format for $DISTRO. Using portable binary."
|
|
||||||
mkdir -p /opt /usr/local/bin
|
|
||||||
tar -xzC /opt -f "$EXTRACT_DIR/zupt-${VERSION}-x86_64.AppDir.tar.gz"
|
|
||||||
ln -sf "/opt/zupt-${VERSION}-x86_64.AppDir/AppRun" /usr/local/bin/zupt
|
|
||||||
ok "zupt CLI installed (portable mode)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Install zupt-gui ────────────────────────────────────────────────
|
|
||||||
install_gui() {
|
|
||||||
step "Installing zupt-gui ${GUI_VERSION}"
|
|
||||||
if [ $DEB_BASED -eq 1 ]; then
|
|
||||||
dpkg -i "$EXTRACT_DIR/zupt-gui_${GUI_VERSION}_all.deb" 2>&1 \
|
|
||||||
| grep -v '^Selecting\|^Preparing\|^Unpacking\|^Setting up\|^Processing' || true
|
|
||||||
apt-get -f install -y 2>/dev/null || true
|
|
||||||
ok "zupt-gui installed"
|
|
||||||
elif [ $RPM_BASED -eq 1 ]; then
|
|
||||||
local rpmtar="$EXTRACT_DIR/zupt-gui-${GUI_VERSION}.srpm.tar.gz"
|
|
||||||
if command -v rpmbuild >/dev/null; then
|
|
||||||
local rpmroot=$(mktemp -d)
|
|
||||||
tar -xzC "$rpmroot" -f "$rpmtar"
|
|
||||||
rpmbuild --define "_topdir $rpmroot" -bb "$rpmroot/SPECS/zupt-gui.spec"
|
|
||||||
rpm -Uvh --force "$rpmroot"/RPMS/noarch/zupt-gui-*.rpm
|
|
||||||
rm -rf "$rpmroot"
|
|
||||||
else
|
|
||||||
warn "rpmbuild missing — using portable mode"
|
|
||||||
mkdir -p /opt /usr/local/bin
|
|
||||||
tar -xzC /opt -f "$EXTRACT_DIR/Zupt-GUI-${GUI_VERSION}-x86_64.AppDir.tar.gz"
|
|
||||||
ln -sf "/opt/zupt-gui.AppDir/AppRun" /usr/local/bin/zupt-gui
|
|
||||||
ok "zupt-gui installed (portable)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# Portable
|
|
||||||
mkdir -p /opt /usr/local/bin
|
|
||||||
tar -xzC /opt -f "$EXTRACT_DIR/Zupt-GUI-${GUI_VERSION}-x86_64.AppDir.tar.gz"
|
|
||||||
ln -sf "/opt/zupt-gui.AppDir/AppRun" /usr/local/bin/zupt-gui
|
|
||||||
# Desktop integration if possible
|
|
||||||
if [ -d /usr/share/applications ]; then
|
|
||||||
cp /opt/zupt-gui.AppDir/zupt-gui.desktop /usr/share/applications/ 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
ok "zupt-gui installed (portable)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── AppImage extract (no install) ───────────────────────────────────
|
|
||||||
install_appimage() {
|
|
||||||
step "Extracting AppImage to current directory"
|
|
||||||
local target="${PWD}/zupt-portable"
|
|
||||||
mkdir -p "$target"
|
|
||||||
tar -xzC "$target" -f "$EXTRACT_DIR/zupt-${VERSION}-x86_64.AppDir.tar.gz"
|
|
||||||
tar -xzC "$target" -f "$EXTRACT_DIR/Zupt-GUI-${GUI_VERSION}-x86_64.AppDir.tar.gz"
|
|
||||||
cat > "$target/zupt" <<EOF
|
|
||||||
#!/bin/sh
|
|
||||||
exec "$target/zupt-${VERSION}-x86_64.AppDir/AppRun" "\$@"
|
|
||||||
EOF
|
|
||||||
cat > "$target/zupt-gui" <<EOF
|
|
||||||
#!/bin/sh
|
|
||||||
exec "$target/zupt-gui.AppDir/AppRun" "\$@"
|
|
||||||
EOF
|
|
||||||
chmod +x "$target/zupt" "$target/zupt-gui"
|
|
||||||
ok "Portable install at: $target"
|
|
||||||
echo "Run: $target/zupt help"
|
|
||||||
echo " $target/zupt-gui"
|
|
||||||
echo
|
|
||||||
warn "Portable mode still needs Python 3 + PyQt6 system-wide."
|
|
||||||
warn "To install Qt6: sudo apt install python3-pyqt6 (or equivalent)"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Uninstall ───────────────────────────────────────────────────────
|
|
||||||
do_uninstall() {
|
|
||||||
step "Uninstalling zupt + zupt-gui"
|
|
||||||
if [ $DEB_BASED -eq 1 ]; then
|
|
||||||
dpkg -r zupt-gui 2>/dev/null || true
|
|
||||||
dpkg -r zupt 2>/dev/null || true
|
|
||||||
elif [ $RPM_BASED -eq 1 ]; then
|
|
||||||
rpm -e zupt-gui 2>/dev/null || true
|
|
||||||
rpm -e zupt 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
rm -rf /opt/zupt-2.2.3-x86_64.AppDir /opt/zupt-gui.AppDir 2>/dev/null
|
|
||||||
rm -f /usr/local/bin/zupt /usr/local/bin/zupt-gui 2>/dev/null
|
|
||||||
rm -f /usr/share/applications/zupt-gui.desktop 2>/dev/null
|
|
||||||
ok "Uninstall complete"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────
|
|
||||||
# MAIN
|
|
||||||
# ─────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
cat <<HEADER
|
|
||||||
${BOLD}${CYAN}
|
|
||||||
╔══════════════════════════════════════════════════════════════╗
|
|
||||||
║ ZUPT ${VERSION} + ZUPT-GUI ${GUI_VERSION} ║
|
|
||||||
║ Post-quantum backup compression — Linux installer ║
|
|
||||||
╚══════════════════════════════════════════════════════════════╝
|
|
||||||
${RESET}
|
|
||||||
Distribution detected: ${BOLD}${DISTRO_NAME}${RESET}
|
|
||||||
Mode: ${BOLD}${MODE}${RESET}
|
|
||||||
|
|
||||||
HEADER
|
|
||||||
|
|
||||||
if [ "$MODE" = "uninstall" ]; then
|
|
||||||
do_uninstall
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
extract_payload
|
|
||||||
|
|
||||||
case "$MODE" in
|
|
||||||
cli)
|
|
||||||
install_cli ;;
|
|
||||||
gui)
|
|
||||||
install_qt6
|
|
||||||
install_gui ;;
|
|
||||||
full)
|
|
||||||
install_qt6
|
|
||||||
install_cli
|
|
||||||
install_gui ;;
|
|
||||||
appimage)
|
|
||||||
install_appimage ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo
|
|
||||||
step "Installation complete"
|
|
||||||
case "$MODE" in
|
|
||||||
cli|full) echo " Run: zupt help" ;;
|
|
||||||
esac
|
|
||||||
case "$MODE" in
|
|
||||||
gui|full) echo " Run: zupt-gui (or find 'Zupt GUI' in your applications menu)" ;;
|
|
||||||
esac
|
|
||||||
case "$MODE" in
|
|
||||||
appimage) echo " Run: ./zupt-portable/zupt help" ;;
|
|
||||||
esac
|
|
||||||
echo
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
863
scripts/check-source-only.sh
Executable file
863
scripts/check-source-only.sh
Executable file
|
|
@ -0,0 +1,863 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
# Audit ZUPT source trees and archives for compiled or unsafe content.
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
PROGRAM=${0##*/}
|
||||||
|
ROOT=
|
||||||
|
ROOT_REQUESTED=0
|
||||||
|
DATA_MANIFEST=
|
||||||
|
REPOSITORY_AUDIT=1
|
||||||
|
HAVE_EXTERNAL_TARGET=0
|
||||||
|
declare -a TAGS=()
|
||||||
|
declare -a ARCHIVES=()
|
||||||
|
declare -a TREES=()
|
||||||
|
|
||||||
|
FAILURES=0
|
||||||
|
SCANNED=0
|
||||||
|
ARCHIVES_SCANNED=0
|
||||||
|
MAX_ARCHIVE_DEPTH=${SOURCE_AUDIT_MAX_DEPTH:-5}
|
||||||
|
MAX_ARCHIVES=${SOURCE_AUDIT_MAX_ARCHIVES:-1000}
|
||||||
|
MAX_ARCHIVE_MEMBERS=${SOURCE_AUDIT_MAX_MEMBERS:-10000}
|
||||||
|
MAX_ARCHIVE_LIST_KIB=${SOURCE_AUDIT_MAX_LIST_KIB:-16384}
|
||||||
|
MAX_ARCHIVE_KIB=${SOURCE_AUDIT_MAX_KIB:-524288}
|
||||||
|
MAX_TOTAL_ARCHIVE_KIB=${SOURCE_AUDIT_MAX_TOTAL_KIB:-1048576}
|
||||||
|
ARCHIVE_TIMEOUT_SECONDS=${SOURCE_AUDIT_ARCHIVE_SECONDS:-60}
|
||||||
|
FORCE_PORTABLE_WATCHDOG=${SOURCE_AUDIT_FORCE_WATCHDOG:-0}
|
||||||
|
TOTAL_ARCHIVE_BYTES=0
|
||||||
|
|
||||||
|
for limit in "$MAX_ARCHIVE_DEPTH" "$MAX_ARCHIVES" "$MAX_ARCHIVE_MEMBERS" \
|
||||||
|
"$MAX_ARCHIVE_LIST_KIB" "$MAX_ARCHIVE_KIB" \
|
||||||
|
"$MAX_TOTAL_ARCHIVE_KIB" "$ARCHIVE_TIMEOUT_SECONDS"; do
|
||||||
|
[[ $limit =~ ^[0-9]+$ ]] || {
|
||||||
|
printf 'ERROR: source-audit limits must be non-negative integers\n' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
done
|
||||||
|
[[ $FORCE_PORTABLE_WATCHDOG == 0 || $FORCE_PORTABLE_WATCHDOG == 1 ]] || {
|
||||||
|
printf 'ERROR: SOURCE_AUDIT_FORCE_WATCHDOG must be 0 or 1\n' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
((ARCHIVE_TIMEOUT_SECONDS > 0)) || {
|
||||||
|
printf 'ERROR: source-audit archive timeout must be positive\n' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
((MAX_ARCHIVE_LIST_KIB <= 2147483647 && MAX_ARCHIVE_KIB <= 2147483647 &&
|
||||||
|
MAX_TOTAL_ARCHIVE_KIB <= 2147483647)) || {
|
||||||
|
printf 'ERROR: source-audit KiB limits are too large for safe accounting\n' >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
AUDIT_TMP=$(mktemp -d "${TMPDIR:-/tmp}/zupt-source-audit.XXXXXXXX")
|
||||||
|
# shellcheck disable=SC2317 # Invoked indirectly by trap.
|
||||||
|
cleanup() {
|
||||||
|
local status=$?
|
||||||
|
trap - EXIT HUP INT TERM
|
||||||
|
rm -rf -- "$AUDIT_TMP"
|
||||||
|
exit "$status"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
|
for required_tool in file od tr grep find head wc awk; do
|
||||||
|
if ! command -v "$required_tool" >/dev/null 2>&1; then
|
||||||
|
printf 'ERROR: source audit requires %s\n' "$required_tool" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
unset required_tool
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $PROGRAM [--root DIR] [--tag REV] [--archive FILE] [--tree DIR]
|
||||||
|
[--data-manifest FILE]
|
||||||
|
|
||||||
|
Without an external target, audit the Git index, the complete working tree,
|
||||||
|
and git archive HEAD. --tag adds an immutable Git revision archive to that
|
||||||
|
repository audit. --archive and --tree audit standalone inputs and do not
|
||||||
|
require a Git repository unless --tag is also supplied. A data manifest may
|
||||||
|
allow a necessary .bin fixture using four tab-separated fields per record:
|
||||||
|
path, purpose, provenance, and SPDX license. Magic-byte checks still apply.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
while (($#)); do
|
||||||
|
case $1 in
|
||||||
|
--root)
|
||||||
|
(($# >= 2)) || { printf 'ERROR: --root requires a directory\n' >&2; exit 2; }
|
||||||
|
ROOT=$2
|
||||||
|
ROOT_REQUESTED=1
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--tag)
|
||||||
|
(($# >= 2)) || { printf 'ERROR: --tag requires a revision\n' >&2; exit 2; }
|
||||||
|
TAGS+=("$2")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--archive)
|
||||||
|
(($# >= 2)) || { printf 'ERROR: --archive requires a file\n' >&2; exit 2; }
|
||||||
|
ARCHIVES+=("$2")
|
||||||
|
HAVE_EXTERNAL_TARGET=1
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--tree)
|
||||||
|
(($# >= 2)) || { printf 'ERROR: --tree requires a directory\n' >&2; exit 2; }
|
||||||
|
TREES+=("$2")
|
||||||
|
HAVE_EXTERNAL_TARGET=1
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--data-manifest)
|
||||||
|
(($# >= 2)) || { printf 'ERROR: --data-manifest requires a file\n' >&2; exit 2; }
|
||||||
|
DATA_MANIFEST=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
(($# == 0)) || { printf 'ERROR: unexpected operand\n' >&2; exit 2; }
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf 'ERROR: unknown option: %s\n' "$1" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if ((HAVE_EXTERNAL_TARGET)) && ((ROOT_REQUESTED == 0)) && ((${#TAGS[@]} == 0)); then
|
||||||
|
REPOSITORY_AUDIT=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
unicode_format_control() {
|
||||||
|
local codepoint=$1
|
||||||
|
((codepoint == 0x00ad ||
|
||||||
|
(codepoint >= 0x0600 && codepoint <= 0x0605) ||
|
||||||
|
codepoint == 0x061c || codepoint == 0x06dd || codepoint == 0x070f ||
|
||||||
|
(codepoint >= 0x0890 && codepoint <= 0x0891) ||
|
||||||
|
codepoint == 0x08e2 || codepoint == 0x180e ||
|
||||||
|
(codepoint >= 0x200b && codepoint <= 0x200f) ||
|
||||||
|
(codepoint >= 0x202a && codepoint <= 0x202e) ||
|
||||||
|
(codepoint >= 0x2060 && codepoint <= 0x206f) ||
|
||||||
|
codepoint == 0xfeff ||
|
||||||
|
(codepoint >= 0xfff9 && codepoint <= 0xfffb) ||
|
||||||
|
codepoint == 0x110bd || codepoint == 0x110cd ||
|
||||||
|
(codepoint >= 0x13430 && codepoint <= 0x1343f) ||
|
||||||
|
(codepoint >= 0x1bca0 && codepoint <= 0x1bca3) ||
|
||||||
|
(codepoint >= 0x1d173 && codepoint <= 0x1d17a) ||
|
||||||
|
codepoint == 0xe0001 ||
|
||||||
|
(codepoint >= 0xe0020 && codepoint <= 0xe007f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
safe_path_for_output() {
|
||||||
|
local path=$1
|
||||||
|
local output='' character='' sequence='' escaped=''
|
||||||
|
local LC_ALL=C byte byte2 byte3 byte4 codepoint index length
|
||||||
|
length=${#path}
|
||||||
|
for ((index = 0; index < length; index++)); do
|
||||||
|
character=${path:index:1}
|
||||||
|
printf -v byte '%d' "'$character"
|
||||||
|
|
||||||
|
if ((byte < 0x20 || byte == 0x7f)); then
|
||||||
|
printf -v escaped '\\x%02x' "$byte"
|
||||||
|
output+=$escaped
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if ((byte < 0x80)); then
|
||||||
|
if [[ $character == \\ ]]; then
|
||||||
|
output+="${character}${character}"
|
||||||
|
else
|
||||||
|
output+=$character
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
codepoint=0
|
||||||
|
sequence=
|
||||||
|
if ((byte >= 0xc2 && byte <= 0xdf && index + 1 < length)); then
|
||||||
|
character=${path:index+1:1}
|
||||||
|
printf -v byte2 '%d' "'$character"
|
||||||
|
if ((byte2 >= 0x80 && byte2 <= 0xbf)); then
|
||||||
|
codepoint=$(((byte & 0x1f) << 6 | (byte2 & 0x3f)))
|
||||||
|
sequence=${path:index:2}
|
||||||
|
fi
|
||||||
|
elif ((byte >= 0xe0 && byte <= 0xef && index + 2 < length)); then
|
||||||
|
character=${path:index+1:1}
|
||||||
|
printf -v byte2 '%d' "'$character"
|
||||||
|
character=${path:index+2:1}
|
||||||
|
printf -v byte3 '%d' "'$character"
|
||||||
|
if ((byte3 >= 0x80 && byte3 <= 0xbf &&
|
||||||
|
((byte == 0xe0 && byte2 >= 0xa0 && byte2 <= 0xbf) ||
|
||||||
|
(byte >= 0xe1 && byte <= 0xec && byte2 >= 0x80 && byte2 <= 0xbf) ||
|
||||||
|
(byte == 0xed && byte2 >= 0x80 && byte2 <= 0x9f) ||
|
||||||
|
(byte >= 0xee && byte <= 0xef && byte2 >= 0x80 && byte2 <= 0xbf)))); then
|
||||||
|
codepoint=$(((byte & 0x0f) << 12 | (byte2 & 0x3f) << 6 |
|
||||||
|
(byte3 & 0x3f)))
|
||||||
|
sequence=${path:index:3}
|
||||||
|
fi
|
||||||
|
elif ((byte >= 0xf0 && byte <= 0xf4 && index + 3 < length)); then
|
||||||
|
character=${path:index+1:1}
|
||||||
|
printf -v byte2 '%d' "'$character"
|
||||||
|
character=${path:index+2:1}
|
||||||
|
printf -v byte3 '%d' "'$character"
|
||||||
|
character=${path:index+3:1}
|
||||||
|
printf -v byte4 '%d' "'$character"
|
||||||
|
if ((byte3 >= 0x80 && byte3 <= 0xbf &&
|
||||||
|
byte4 >= 0x80 && byte4 <= 0xbf &&
|
||||||
|
((byte == 0xf0 && byte2 >= 0x90 && byte2 <= 0xbf) ||
|
||||||
|
(byte >= 0xf1 && byte <= 0xf3 && byte2 >= 0x80 && byte2 <= 0xbf) ||
|
||||||
|
(byte == 0xf4 && byte2 >= 0x80 && byte2 <= 0x8f)))); then
|
||||||
|
codepoint=$(((byte & 0x07) << 18 | (byte2 & 0x3f) << 12 |
|
||||||
|
(byte3 & 0x3f) << 6 | (byte4 & 0x3f)))
|
||||||
|
sequence=${path:index:4}
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z $sequence ]]; then
|
||||||
|
printf -v escaped '\\x%02x' "$byte"
|
||||||
|
output+=$escaped
|
||||||
|
elif ((codepoint >= 0x80 && codepoint <= 0x9f)) ||
|
||||||
|
((codepoint >= 0x2028 && codepoint <= 0x2029)) ||
|
||||||
|
unicode_format_control "$codepoint"; then
|
||||||
|
if ((codepoint <= 0xffff)); then
|
||||||
|
printf -v escaped '\\u%04x' "$codepoint"
|
||||||
|
else
|
||||||
|
printf -v escaped '\\U%08x' "$codepoint"
|
||||||
|
fi
|
||||||
|
output+=$escaped
|
||||||
|
index=$((index + ${#sequence} - 1))
|
||||||
|
else
|
||||||
|
output+=$sequence
|
||||||
|
index=$((index + ${#sequence} - 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf '%s' "$output"
|
||||||
|
}
|
||||||
|
|
||||||
|
canonicalize_allow_missing() {
|
||||||
|
local path=$1
|
||||||
|
if realpath -m -- / >/dev/null 2>&1; then
|
||||||
|
realpath -m -- "$path"
|
||||||
|
elif command -v python3 >/dev/null 2>&1; then
|
||||||
|
python3 - "$path" <<'PY'
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
print(os.path.realpath(sys.argv[1]))
|
||||||
|
PY
|
||||||
|
else
|
||||||
|
printf 'ERROR: canonical path checking needs GNU realpath or python3\n' >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
fail_path() {
|
||||||
|
local scope=$1 path=$2 reason=$3
|
||||||
|
FAILURES=$((FAILURES + 1))
|
||||||
|
printf 'FAIL [%s] %s (%s)\n' "$scope" "$(safe_path_for_output "$path")" "$reason"
|
||||||
|
}
|
||||||
|
|
||||||
|
path_stays_below_root() {
|
||||||
|
local candidate=${1//\\//}
|
||||||
|
local component
|
||||||
|
local depth=0
|
||||||
|
local IFS=/
|
||||||
|
local -a components=()
|
||||||
|
|
||||||
|
[[ $candidate != /* && $candidate != //* ]] || return 1
|
||||||
|
[[ ! $candidate =~ ^[[:alpha:]]: ]] || return 1
|
||||||
|
read -r -a components <<< "$candidate"
|
||||||
|
for component in "${components[@]}"; do
|
||||||
|
case $component in
|
||||||
|
''|.) ;;
|
||||||
|
..)
|
||||||
|
((depth > 0)) || return 1
|
||||||
|
depth=$((depth - 1))
|
||||||
|
;;
|
||||||
|
*) depth=$((depth + 1)) ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
check_link_target() {
|
||||||
|
local entry=$1 target=$2 scope=$3 display=${4:-$1}
|
||||||
|
local parent combined
|
||||||
|
|
||||||
|
[[ $target != /* && $target != //* && ! $target =~ ^[[:alpha:]]: ]] || {
|
||||||
|
fail_path "$scope" "$display" 'absolute symlink target'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
parent=${entry%/*}
|
||||||
|
[[ $parent != "$entry" ]] || parent=.
|
||||||
|
combined=$parent/$target
|
||||||
|
if ! path_stays_below_root "$combined"; then
|
||||||
|
fail_path "$scope" "$display" 'symlink escapes audit root'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
forbidden_extension() {
|
||||||
|
local path=$1 lower
|
||||||
|
lower=$(LC_ALL=C printf '%s' "${path##*/}" | tr '[:upper:]' '[:lower:]')
|
||||||
|
case $lower in
|
||||||
|
*.o|*.obj|*.so|*.so.*|*.a|*.la|*.dll|*.dylib|*.exe|*.com|\
|
||||||
|
*.class|*.jar|*.war|*.wasm|*.pyc|*.pyo|*.rpm|*.deb|*.appimage|\
|
||||||
|
*.msi|*.apk|*.ipa|*.dmg|*.elf|*.ko|*.mod|*.lib|*.pdb|*.out)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
is_declared_binary_data() {
|
||||||
|
local logical=$1 candidate line path purpose provenance license extra
|
||||||
|
[[ -n $DATA_MANIFEST && -r $DATA_MANIFEST ]] || return 1
|
||||||
|
candidate=${logical##*!}
|
||||||
|
while IFS= read -r line || [[ -n $line ]]; do
|
||||||
|
[[ -n $line && ${line:0:1} != '#' ]] || continue
|
||||||
|
IFS=$'\t' read -r path purpose provenance license extra <<< "$line"
|
||||||
|
if [[ $path == "$candidate" && -n $purpose && -n $provenance &&
|
||||||
|
-n $license && -z ${extra:-} ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < "$DATA_MANIFEST"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
magic_kind() {
|
||||||
|
local file=$1 hex machine sections flags
|
||||||
|
hex=$(LC_ALL=C od -An -v -tx1 -N 512 "$file" 2>/dev/null | tr -d '[:space:]') || return 1
|
||||||
|
[[ -n $hex ]] || return 1
|
||||||
|
|
||||||
|
if [[ $hex == 7f454c46* && ${hex:16:6} =~ ^4149(01|02)$ ]]; then
|
||||||
|
printf 'AppImage executable'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
case $hex in
|
||||||
|
7f454c46*) printf 'ELF executable or object'; return 0 ;;
|
||||||
|
4d5a*) printf 'PE/MZ executable'; return 0 ;;
|
||||||
|
feedface*|cefaedfe*|feedfacf*|cffaedfe*|cafebabe*|bebafeca*|cafebabf*|bfbafeca*)
|
||||||
|
printf 'Mach-O, universal binary, or Java class'; return 0 ;;
|
||||||
|
213c617263683e0a64656269616e2d62696e617279*) printf 'Debian package'; return 0 ;;
|
||||||
|
213c617263683e0a*) printf 'ar archive or static library'; return 0 ;;
|
||||||
|
213c7468696e3e0a*) printf 'GNU thin archive or static library'; return 0 ;;
|
||||||
|
edabeedb*) printf 'RPM package'; return 0 ;;
|
||||||
|
0061736d*) printf 'WebAssembly bytecode'; return 0 ;;
|
||||||
|
6465780a*) printf 'Dalvik bytecode'; return 0 ;;
|
||||||
|
1b4c7561*) printf 'Lua bytecode'; return 0 ;;
|
||||||
|
4243c0de*) printf 'LLVM bitcode'; return 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# CPython bytecode starts with a version magic ending in CRLF, followed by
|
||||||
|
# a small flags word. Requiring the complete 16-byte header avoids treating
|
||||||
|
# ordinary text beginning with CRLF as bytecode.
|
||||||
|
if ((${#hex} >= 32)) && [[ ${hex:4:4} == 0d0a ]] &&
|
||||||
|
[[ ${hex:8:8} =~ ^(00000000|01000000|02000000|03000000)$ ]]; then
|
||||||
|
printf 'Python bytecode'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A COFF object starts with a known machine identifier and a non-zero,
|
||||||
|
# reasonably bounded section count in its fixed-size 20-byte header.
|
||||||
|
if ((${#hex} >= 40)); then
|
||||||
|
machine=${hex:0:4}
|
||||||
|
sections=${hex:4:4}
|
||||||
|
flags=${hex:32:8}
|
||||||
|
case $machine in
|
||||||
|
4c01|6486|c001|c201|c401|64aa|6601|f001|f701|bc0e|5001|d301)
|
||||||
|
if [[ $sections != 0000 && $sections != 00000000 && $flags =~ ^[[:xdigit:]]{8}$ ]]; then
|
||||||
|
printf 'COFF object'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
file_utility_kind() {
|
||||||
|
local file=$1 description mime
|
||||||
|
command -v file >/dev/null 2>&1 || return 1
|
||||||
|
description=$(LC_ALL=C file -b "$file" 2>/dev/null) || return 1
|
||||||
|
mime=$(LC_ALL=C file -b --mime-type "$file" 2>/dev/null) || mime=
|
||||||
|
case $description in
|
||||||
|
*ELF*) printf 'ELF executable or object'; return 0 ;;
|
||||||
|
*PE32*|*MS-DOS\ executable*) printf 'PE/MZ executable'; return 0 ;;
|
||||||
|
*Mach-O*|*COFF*) printf 'Mach-O or COFF compiled code'; return 0 ;;
|
||||||
|
*RPM*package*|*Debian\ binary\ package*) printf 'binary distribution package'; return 0 ;;
|
||||||
|
*current\ ar\ archive*|*thin\ archive*)
|
||||||
|
printf 'ar archive or static library'; return 0 ;;
|
||||||
|
esac
|
||||||
|
case $mime in
|
||||||
|
application/x-executable|application/x-pie-executable|application/x-sharedlib|\
|
||||||
|
application/x-object|application/x-archive|application/x-dosexec|\
|
||||||
|
application/x-rpm|application/vnd.debian.binary-package|application/wasm|\
|
||||||
|
application/java-vm)
|
||||||
|
printf 'compiled code or binary package'
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
looks_like_archive() {
|
||||||
|
local file=$1 logical=$2 hex lower
|
||||||
|
lower=$(LC_ALL=C printf '%s' "$logical" | tr '[:upper:]' '[:lower:]')
|
||||||
|
case $lower in
|
||||||
|
*.tar|*.tar.gz|*.tgz|*.tar.xz|*.txz|*.tar.bz2|*.tbz|*.tbz2|\
|
||||||
|
*.tar.zst|*.tzst|*.zip|*.jar|*.war|*.deb|*.apk|*.ipa|*.cpio)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*.7z|*.rar)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
hex=$(LC_ALL=C od -An -v -tx1 -N 512 "$file" 2>/dev/null | tr -d '[:space:]') || return 1
|
||||||
|
case $hex in
|
||||||
|
504b0304*|504b0506*|504b0708*|1f8b*|425a68*|fd377a585a00*|\
|
||||||
|
28b52ffd*|213c617263683e0a*|213c7468696e3e0a*|edabeedb*|3037303730*|\
|
||||||
|
377abcaf271c*|526172211a0700*|526172211a070100*) return 0 ;;
|
||||||
|
esac
|
||||||
|
[[ ${hex:514:10} == 7573746172 ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
is_reference_source() {
|
||||||
|
local logical=$1 base=${1##*/}
|
||||||
|
case $logical in
|
||||||
|
*scripts/check-source-only.sh|*tests/test_source_only.sh|\
|
||||||
|
*packaging/opensuse/source-audit.sh)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case $base in
|
||||||
|
Makefile|makefile|GNUmakefile|CMakeLists.txt|*.mk|*.cmake|*.sh|*.bash|\
|
||||||
|
*.c|*.h|*.cc|*.hh|*.cpp|*.hpp|*.py|*.pl|*.rb|*.spec|*.service|\
|
||||||
|
*.yml|*.yaml|Dockerfile|Containerfile)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
check_removed_library_reference() {
|
||||||
|
local file=$1 logical=$2 scope=$3
|
||||||
|
is_reference_source "$logical" || return 0
|
||||||
|
LC_ALL=C grep -Iq . "$file" 2>/dev/null || return 0
|
||||||
|
if LC_ALL=C grep -Eaq -- \
|
||||||
|
'libvuptsdk[.]so|vendor/(vuptsdk|pqvaptvupt)/[^[:space:]"'"'"'`]*[.](so([.][0-9A-Za-z._-]+)?|a|o)([^0-9A-Za-z._-]|$)' \
|
||||||
|
"$file" 2>/dev/null; then
|
||||||
|
fail_path "$scope" "$logical" 'reference to removed vendored library'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
archive_tool() {
|
||||||
|
if command -v bsdtar >/dev/null 2>&1; then
|
||||||
|
printf 'bsdtar'
|
||||||
|
elif command -v tar >/dev/null 2>&1; then
|
||||||
|
printf 'tar'
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_archive_command() {
|
||||||
|
local command_pid watchdog_pid status
|
||||||
|
if [[ $FORCE_PORTABLE_WATCHDOG == 0 ]] && \
|
||||||
|
command -v timeout >/dev/null 2>&1 && \
|
||||||
|
timeout --help 2>&1 | grep -F -- '--kill-after' >/dev/null; then
|
||||||
|
timeout --kill-after=2 "${ARCHIVE_TIMEOUT_SECONDS}s" "$@"
|
||||||
|
else
|
||||||
|
"$@" &
|
||||||
|
command_pid=$!
|
||||||
|
(
|
||||||
|
local elapsed=0
|
||||||
|
while kill -0 "$command_pid" 2>/dev/null; do
|
||||||
|
if ((elapsed >= ARCHIVE_TIMEOUT_SECONDS)); then
|
||||||
|
kill -TERM "$command_pid" 2>/dev/null || exit 0
|
||||||
|
sleep 1
|
||||||
|
kill -KILL "$command_pid" 2>/dev/null || true
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
elapsed=$((elapsed + 1))
|
||||||
|
done
|
||||||
|
) &
|
||||||
|
watchdog_pid=$!
|
||||||
|
if wait "$command_pid"; then status=0; else status=$?; fi
|
||||||
|
kill "$watchdog_pid" 2>/dev/null || true
|
||||||
|
wait "$watchdog_pid" 2>/dev/null || true
|
||||||
|
return "$status"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
archive_declared_bytes() {
|
||||||
|
local tool=$1 verbose=$2 size_field=3
|
||||||
|
if "$tool" --version 2>/dev/null | grep -Eqi 'bsdtar|libarchive'; then
|
||||||
|
size_field=5
|
||||||
|
fi
|
||||||
|
awk -v field="$size_field" -v max_kib="$MAX_ARCHIVE_KIB" '
|
||||||
|
BEGIN { total = 0; status = 0; max = max_kib * 1024 }
|
||||||
|
{
|
||||||
|
if (NF < field || $field !~ /^[0-9]+$/) {
|
||||||
|
status = 2
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
size = $field + 0
|
||||||
|
if (size > max - total) {
|
||||||
|
status = 3
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
total += size
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
if (status == 0) printf "%.0f\n", total
|
||||||
|
exit status
|
||||||
|
}
|
||||||
|
' "$verbose"
|
||||||
|
}
|
||||||
|
|
||||||
|
extracted_regular_bytes() {
|
||||||
|
local root=$1 file size total=0 max_bytes=$((MAX_ARCHIVE_KIB * 1024))
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
size=$(LC_ALL=C wc -c <"$file" | tr -d '[:space:]')
|
||||||
|
[[ $size =~ ^[0-9]+$ ]] || return 2
|
||||||
|
((size <= max_bytes - total)) || return 3
|
||||||
|
total=$((total + size))
|
||||||
|
done < <(find -P "$root" -type f -print0)
|
||||||
|
printf '%s\n' "$total"
|
||||||
|
}
|
||||||
|
|
||||||
|
scan_archive() {
|
||||||
|
local archive=$1 logical=$2 scope=$3 depth=$4
|
||||||
|
local tool archive_dir list verbose extract_dir member vline target count validation_start
|
||||||
|
local list_limit_marker verbose_limit_marker declared_bytes actual_bytes
|
||||||
|
local file_limit_blocks status limit_reason
|
||||||
|
local max_total_bytes
|
||||||
|
|
||||||
|
if ((depth > MAX_ARCHIVE_DEPTH)); then
|
||||||
|
fail_path "$scope" "$logical" 'nested archive depth limit exceeded'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! tool=$(archive_tool); then
|
||||||
|
fail_path "$scope" "$logical" 'no supported archive inspection tool'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((ARCHIVES_SCANNED >= MAX_ARCHIVES)); then
|
||||||
|
fail_path "$scope" "$logical" 'global archive count limit exceeded'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
ARCHIVES_SCANNED=$((ARCHIVES_SCANNED + 1))
|
||||||
|
archive_dir=$(mktemp -d "$AUDIT_TMP/archive.XXXXXXXX")
|
||||||
|
list=$archive_dir/list
|
||||||
|
verbose=$archive_dir/verbose
|
||||||
|
extract_dir=$archive_dir/root
|
||||||
|
list_limit_marker=$archive_dir/member-limit
|
||||||
|
verbose_limit_marker=$archive_dir/metadata-limit
|
||||||
|
mkdir -p "$extract_dir"
|
||||||
|
|
||||||
|
if ! run_archive_command "$tool" -tf "$archive" 2>/dev/null | \
|
||||||
|
head -c "$((MAX_ARCHIVE_LIST_KIB * 1024 + 1))" | awk \
|
||||||
|
-v max="$MAX_ARCHIVE_MEMBERS" \
|
||||||
|
-v max_bytes="$((MAX_ARCHIVE_LIST_KIB * 1024))" \
|
||||||
|
-v marker="$list_limit_marker" '
|
||||||
|
{ bytes += length($0) + 1 }
|
||||||
|
bytes > max_bytes {
|
||||||
|
print "archive member-name budget exceeded" > marker
|
||||||
|
exit 43
|
||||||
|
}
|
||||||
|
NR > max {
|
||||||
|
print "archive member limit exceeded" > marker
|
||||||
|
exit 42
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' >"$list"; then
|
||||||
|
if [[ -s $list_limit_marker ]]; then
|
||||||
|
limit_reason=$(<"$list_limit_marker")
|
||||||
|
fail_path "$scope" "$logical" "$limit_reason"
|
||||||
|
else
|
||||||
|
fail_path "$scope" "$logical" 'archive cannot be listed safely'
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
count=$(LC_ALL=C wc -l <"$list" | tr -d '[:space:]')
|
||||||
|
if ((count == 0)); then
|
||||||
|
fail_path "$scope" "$logical" 'archive has no inspectable members'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ((count > MAX_ARCHIVE_MEMBERS)); then
|
||||||
|
fail_path "$scope" "$logical" 'archive member limit exceeded'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
validation_start=$FAILURES
|
||||||
|
while IFS= read -r member || [[ -n $member ]]; do
|
||||||
|
if ! path_stays_below_root "$member"; then
|
||||||
|
fail_path "$scope" "$logical!$member" 'archive member escapes extraction root'
|
||||||
|
fi
|
||||||
|
done <"$list"
|
||||||
|
|
||||||
|
if ! run_archive_command "$tool" -tvf "$archive" 2>/dev/null | \
|
||||||
|
head -c "$((MAX_ARCHIVE_LIST_KIB * 2048 + 1))" | awk \
|
||||||
|
-v max="$count" -v max_bytes="$((MAX_ARCHIVE_LIST_KIB * 2048))" \
|
||||||
|
-v marker="$verbose_limit_marker" '
|
||||||
|
{ bytes += length($0) + 1 }
|
||||||
|
bytes > max_bytes || NR > max {
|
||||||
|
print "archive metadata output limit exceeded" > marker
|
||||||
|
exit 44
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
' >"$verbose"; then
|
||||||
|
if [[ -s $verbose_limit_marker ]]; then
|
||||||
|
limit_reason=$(<"$verbose_limit_marker")
|
||||||
|
fail_path "$scope" "$logical" "$limit_reason"
|
||||||
|
else
|
||||||
|
fail_path "$scope" "$logical" 'archive metadata cannot be inspected safely'
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [[ $(wc -l <"$verbose" | tr -d '[:space:]') != "$count" ]]; then
|
||||||
|
fail_path "$scope" "$logical" 'archive metadata does not match member list'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if declared_bytes=$(archive_declared_bytes "$tool" "$verbose"); then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
status=$?
|
||||||
|
if ((status == 3)); then
|
||||||
|
fail_path "$scope" "$logical" 'archive declared-size limit exceeded before extraction'
|
||||||
|
else
|
||||||
|
fail_path "$scope" "$logical" 'archive member sizes cannot be accounted safely'
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
max_total_bytes=$((MAX_TOTAL_ARCHIVE_KIB * 1024))
|
||||||
|
if ((declared_bytes > max_total_bytes - TOTAL_ARCHIVE_BYTES)); then
|
||||||
|
fail_path "$scope" "$logical" 'global archive declared-size budget exceeded'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
TOTAL_ARCHIVE_BYTES=$((TOTAL_ARCHIVE_BYTES + declared_bytes))
|
||||||
|
exec 3<"$list" 4<"$verbose"
|
||||||
|
while IFS= read -r member <&3 || [[ -n $member ]]; do
|
||||||
|
IFS= read -r vline <&4 || vline=
|
||||||
|
case $vline in
|
||||||
|
l*' -> '*)
|
||||||
|
target=${vline##* -> }
|
||||||
|
check_link_target "$member" "$target" "$scope" "$logical!$member"
|
||||||
|
;;
|
||||||
|
h*' link to '*)
|
||||||
|
target=${vline##* link to }
|
||||||
|
if ! path_stays_below_root "$target"; then
|
||||||
|
fail_path "$scope" "$logical!$member" 'hardlink escapes extraction root'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
b*|c*|p*|s*)
|
||||||
|
fail_path "$scope" "$logical!$member" 'special archive member is not source data'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
exec 3<&- 4<&-
|
||||||
|
|
||||||
|
# Keep validation ahead of mutation when presented with hostile input.
|
||||||
|
if ((FAILURES > validation_start)); then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# POSIX file-size limits use 512-byte blocks; twice the KiB limit is a
|
||||||
|
# conservative per-file ceiling. The declared total above remains tighter.
|
||||||
|
file_limit_blocks=$((MAX_ARCHIVE_KIB * 2 + 2))
|
||||||
|
if ! (
|
||||||
|
ulimit -f "$file_limit_blocks" 2>/dev/null || true
|
||||||
|
run_archive_command "$tool" --no-same-owner --no-same-permissions \
|
||||||
|
-xf "$archive" \
|
||||||
|
-C "$extract_dir" > /dev/null 2>&1
|
||||||
|
); then
|
||||||
|
fail_path "$scope" "$logical" 'archive cannot be extracted for inspection'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if actual_bytes=$(extracted_regular_bytes "$extract_dir"); then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
fail_path "$scope" "$logical" 'archive expanded-size limit exceeded'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ((actual_bytes > declared_bytes)); then
|
||||||
|
fail_path "$scope" "$logical" 'archive expanded beyond its declared member sizes'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
scan_tree "$extract_dir" "$scope" "$depth" "$logical"
|
||||||
|
}
|
||||||
|
|
||||||
|
scan_regular() {
|
||||||
|
local file=$1 logical=$2 scope=$3 depth=$4 kind lower
|
||||||
|
SCANNED=$((SCANNED + 1))
|
||||||
|
|
||||||
|
if [[ ! -r $file ]]; then
|
||||||
|
fail_path "$scope" "$logical" 'file cannot be read for audit'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if forbidden_extension "$logical"; then
|
||||||
|
fail_path "$scope" "$logical" 'forbidden compiled/package extension'
|
||||||
|
fi
|
||||||
|
lower=$(LC_ALL=C printf '%s' "$logical" | tr '[:upper:]' '[:lower:]')
|
||||||
|
case $lower in
|
||||||
|
*.bin)
|
||||||
|
if ! is_declared_binary_data "$logical"; then
|
||||||
|
fail_path "$scope" "$logical" \
|
||||||
|
'undeclared .bin data (manifest needs purpose, provenance, and SPDX license)'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if LC_ALL=C grep -Eaqm1 '^version https://git-lfs[.]github[.]com/spec/v1\r?$' "$file" 2>/dev/null; then
|
||||||
|
fail_path "$scope" "$logical" 'unresolved Git LFS pointer'
|
||||||
|
fi
|
||||||
|
if kind=$(magic_kind "$file"); then
|
||||||
|
fail_path "$scope" "$logical" "$kind"
|
||||||
|
elif kind=$(file_utility_kind "$file"); then
|
||||||
|
fail_path "$scope" "$logical" "$kind"
|
||||||
|
fi
|
||||||
|
check_removed_library_reference "$file" "$logical" "$scope"
|
||||||
|
|
||||||
|
if looks_like_archive "$file" "$logical"; then
|
||||||
|
scan_archive "$file" "$logical" "$scope" "$((depth + 1))"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
scan_tree() {
|
||||||
|
local tree=$1 scope=$2 depth=${3:-0} prefix=${4:-}
|
||||||
|
local path relative logical target resolved canonical_tree
|
||||||
|
|
||||||
|
if [[ ! -d $tree ]]; then
|
||||||
|
fail_path "$scope" "$tree" 'tree does not exist'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
canonical_tree=$(canonicalize_allow_missing "$tree") || {
|
||||||
|
fail_path "$scope" "$tree" 'cannot canonicalize audit root'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
while IFS= read -r -d '' path; do
|
||||||
|
relative=${path#"$tree"/}
|
||||||
|
logical=$relative
|
||||||
|
[[ -z $prefix ]] || logical=$prefix!$relative
|
||||||
|
if [[ -L $path ]]; then
|
||||||
|
target=$(readlink "$path")
|
||||||
|
check_link_target "$relative" "$target" "$scope" "$logical"
|
||||||
|
resolved=$(canonicalize_allow_missing "$path") || {
|
||||||
|
fail_path "$scope" "$logical" 'cannot canonicalize symlink'
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case $resolved in
|
||||||
|
"$canonical_tree"|"$canonical_tree"/*) ;;
|
||||||
|
*) fail_path "$scope" "$logical" 'symlink resolves outside audit root' ;;
|
||||||
|
esac
|
||||||
|
elif [[ -f $path ]]; then
|
||||||
|
scan_regular "$path" "$logical" "$scope" "$depth"
|
||||||
|
else
|
||||||
|
fail_path "$scope" "$logical" 'unsupported special filesystem entry'
|
||||||
|
fi
|
||||||
|
done < <(find -P "$tree" -path "$tree/.git" -prune -o \
|
||||||
|
\( -type f -o -type l -o \( ! -type d \) \) -print0)
|
||||||
|
}
|
||||||
|
|
||||||
|
scan_index() {
|
||||||
|
local repo=$1 record metadata logical mode object stage blob target
|
||||||
|
local serial=0
|
||||||
|
|
||||||
|
while IFS= read -r -d '' record; do
|
||||||
|
metadata=${record%%$'\t'*}
|
||||||
|
logical=${record#*$'\t'}
|
||||||
|
read -r mode object stage <<< "$metadata"
|
||||||
|
[[ $stage == 0 ]] || continue
|
||||||
|
case $mode in
|
||||||
|
100*)
|
||||||
|
serial=$((serial + 1))
|
||||||
|
blob=$AUDIT_TMP/index.$serial
|
||||||
|
if git -C "$repo" cat-file blob "$object" >"$blob" 2>/dev/null; then
|
||||||
|
scan_regular "$blob" "$logical" tracked 0
|
||||||
|
else
|
||||||
|
fail_path tracked "$logical" 'cannot read indexed blob'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
120000)
|
||||||
|
if target=$(git -C "$repo" cat-file blob "$object" 2>/dev/null); then
|
||||||
|
check_link_target "$logical" "$target" tracked
|
||||||
|
else
|
||||||
|
fail_path tracked "$logical" 'cannot read indexed symlink'
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
160000) fail_path tracked "$logical" 'Git submodule entry is not source-only' ;;
|
||||||
|
*) fail_path tracked "$logical" 'unsupported Git index mode' ;;
|
||||||
|
esac
|
||||||
|
done < <(git -C "$repo" ls-files --stage -z)
|
||||||
|
}
|
||||||
|
|
||||||
|
scan_git_archive() {
|
||||||
|
local repo=$1 revision=$2 label=$3 tarball
|
||||||
|
tarball=$(mktemp "$AUDIT_TMP/git-archive.XXXXXXXX")
|
||||||
|
if ! git -C "$repo" archive --format=tar "$revision" >"$tarball" 2>/dev/null; then
|
||||||
|
fail_path "$label" "$revision" 'cannot create Git source archive'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
scan_archive "$tarball" "$revision.tar" "$label" 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((REPOSITORY_AUDIT)); then
|
||||||
|
if [[ -z $ROOT ]]; then
|
||||||
|
if ! ROOT=$(git rev-parse --show-toplevel 2>/dev/null); then
|
||||||
|
printf 'ERROR: not inside a Git repository; use --tree or --archive\n' >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if ! ROOT=$(git -C "$ROOT" rev-parse --show-toplevel 2>/dev/null); then
|
||||||
|
printf 'ERROR: --root is not a Git repository\n' >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
scan_index "$ROOT"
|
||||||
|
scan_tree "$ROOT" working-tree 0
|
||||||
|
if git -C "$ROOT" rev-parse --verify -q 'HEAD^{commit}' >/dev/null; then
|
||||||
|
scan_git_archive "$ROOT" HEAD git-archive-HEAD
|
||||||
|
else
|
||||||
|
fail_path git-archive-HEAD HEAD 'repository has no commit'
|
||||||
|
fi
|
||||||
|
for target in "${TAGS[@]}"; do
|
||||||
|
if git -C "$ROOT" rev-parse --verify -q "$target^{commit}" >/dev/null; then
|
||||||
|
scan_git_archive "$ROOT" "$target" "git-archive-$target"
|
||||||
|
else
|
||||||
|
fail_path git-tag "$target" 'revision does not resolve to a commit'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
for target in "${TREES[@]}"; do
|
||||||
|
if [[ -d $target ]]; then
|
||||||
|
target=$(canonicalize_allow_missing "$target") || {
|
||||||
|
fail_path standalone-tree "$target" 'cannot canonicalize tree'
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
scan_tree "$target" standalone-tree 0
|
||||||
|
else
|
||||||
|
fail_path standalone-tree "$target" 'tree does not exist'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for target in "${ARCHIVES[@]}"; do
|
||||||
|
if [[ -f $target ]]; then
|
||||||
|
target=$(canonicalize_allow_missing "$target") || {
|
||||||
|
fail_path standalone-archive "$target" 'cannot canonicalize archive'
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
scan_archive "$target" "${target##*/}" standalone-archive 0
|
||||||
|
else
|
||||||
|
fail_path standalone-archive "$target" 'archive does not exist'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if ((FAILURES == 0)); then
|
||||||
|
printf 'PASS source-only: %d files, %d archives\n' "$SCANNED" "$ARCHIVES_SCANNED"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
printf 'FAIL source-only: %d finding(s), %d files, %d archives\n' \
|
||||||
|
"$FAILURES" "$SCANNED" "$ARCHIVES_SCANNED"
|
||||||
|
exit 1
|
||||||
162
scripts/export-opensuse-package.sh
Executable file
162
scripts/export-opensuse-package.sh
Executable file
|
|
@ -0,0 +1,162 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
umask 077
|
||||||
|
export LC_ALL=C
|
||||||
|
export TZ=UTC
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf 'FAIL: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
need_command() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
for command_name in awk basename bsdtar cat file find git grep mkdir mktemp mv rm sha256sum sort tar touch unzip xargs zip; do
|
||||||
|
need_command "$command_name"
|
||||||
|
done
|
||||||
|
|
||||||
|
repo_root=$(git rev-parse --show-toplevel 2>/dev/null) ||
|
||||||
|
die 'run this script from the ZUPT Git repository'
|
||||||
|
|
||||||
|
cd "$repo_root"
|
||||||
|
|
||||||
|
remote_urls=$(git remote -v | awk '{print $2}' | sort -u)
|
||||||
|
grep -Eq '(^|[/:])cristiancmoises/zupt(\.git)?$' <<<"$remote_urls" ||
|
||||||
|
die 'no configured remote identifies cristiancmoises/zupt'
|
||||||
|
grep -Eqi 'vaptvupt-web|zupt-web' <<<"$remote_urls" &&
|
||||||
|
die 'a configured remote points to a web project'
|
||||||
|
|
||||||
|
version=$(awk -F'"' '/^#define ZUPT_VERSION_STRING / { print $2; exit }' include/zupt.h)
|
||||||
|
[[ -n "$version" ]] || die 'cannot determine version from include/zupt.h'
|
||||||
|
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
|
||||||
|
die "source version is not a stable semantic version: $version"
|
||||||
|
|
||||||
|
release_tag=${1:-v$version}
|
||||||
|
[[ "$release_tag" == "v$version" ]] ||
|
||||||
|
die "tag $release_tag does not match source version v$version"
|
||||||
|
|
||||||
|
tag_ref="refs/tags/$release_tag"
|
||||||
|
git show-ref --verify --quiet "$tag_ref" || die "tag does not exist: $release_tag"
|
||||||
|
[[ $(git cat-file -t "$tag_ref") == tag ]] || die "tag is not annotated: $release_tag"
|
||||||
|
|
||||||
|
head_commit=$(git rev-parse HEAD)
|
||||||
|
tag_commit=$(git rev-parse "$tag_ref^{commit}")
|
||||||
|
[[ "$head_commit" == "$tag_commit" ]] ||
|
||||||
|
die "HEAD $head_commit does not match $release_tag commit $tag_commit"
|
||||||
|
|
||||||
|
if ! git diff --quiet || ! git diff --cached --quiet; then
|
||||||
|
die 'tracked working tree changes must be committed before export'
|
||||||
|
fi
|
||||||
|
|
||||||
|
scanner="$repo_root/scripts/check-source-only.sh"
|
||||||
|
[[ -f "$scanner" ]] || die 'missing scripts/check-source-only.sh'
|
||||||
|
bash "$scanner" --tag "$release_tag"
|
||||||
|
|
||||||
|
git check-ignore -q --no-index dist/ ||
|
||||||
|
die 'dist/ must be ignored before creating the handoff'
|
||||||
|
|
||||||
|
work_dir=$(mktemp -d "${TMPDIR:-/tmp}/zupt-opensuse-export.XXXXXX")
|
||||||
|
cleanup() {
|
||||||
|
if [[ -n ${work_dir:-} && -d ${work_dir:-} ]]; then
|
||||||
|
rm -rf -- "$work_dir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
bundle_name="zupt-openSUSE-source-only-$release_tag"
|
||||||
|
bundle_root="$work_dir/$bundle_name"
|
||||||
|
mkdir -p "$bundle_root"
|
||||||
|
|
||||||
|
git archive "$release_tag" \
|
||||||
|
packaging/opensuse \
|
||||||
|
scripts/check-source-only.sh \
|
||||||
|
scripts/test-installed-zupt.sh \
|
||||||
|
LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-COMMERCIAL \
|
||||||
|
LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0 \
|
||||||
|
NOTICE THIRD-PARTY-NOTICES.md |
|
||||||
|
tar -xf - -C "$bundle_root"
|
||||||
|
|
||||||
|
handoff_legal_files=(
|
||||||
|
LICENSE LICENSE-AGPL-3.0 LICENSE-GPL-3.0 LICENSE-COMMERCIAL
|
||||||
|
LICENSE-BSD-2-Clause LICENSE-BSD-3-Clause LICENSE-CC0-1.0
|
||||||
|
NOTICE THIRD-PARTY-NOTICES.md
|
||||||
|
)
|
||||||
|
for legal_file in "${handoff_legal_files[@]}"; do
|
||||||
|
[[ -s $bundle_root/$legal_file ]] || \
|
||||||
|
die "handoff legal file is missing or empty: $legal_file"
|
||||||
|
done
|
||||||
|
|
||||||
|
cat >"$bundle_root/HANDOFF.md" <<EOF
|
||||||
|
# ZUPT openSUSE source-only handoff
|
||||||
|
|
||||||
|
- Version: $version
|
||||||
|
- Tag: $release_tag
|
||||||
|
- Commit: $tag_commit
|
||||||
|
- Source repository: https://github.com/cristiancmoises/zupt
|
||||||
|
- Package policy: source-only; no RPM, executable, object or library included
|
||||||
|
- Audit entry point: packaging/opensuse/source-audit.sh
|
||||||
|
- License payload: complete public license texts, NOTICE, and third-party record
|
||||||
|
|
||||||
|
Release summary and the validation matrix are recorded in
|
||||||
|
\`packaging/opensuse/zupt.changes\` and
|
||||||
|
\`packaging/opensuse/README.md\`. Run audit commands from this handoff's
|
||||||
|
top-level directory so the wrapper can find \`scripts/check-source-only.sh\`.
|
||||||
|
A result marked SKIP is not a PASS.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
checksum_manifest="$work_dir/SHA256SUMS"
|
||||||
|
(
|
||||||
|
cd "$bundle_root"
|
||||||
|
find . -type f -print0 |
|
||||||
|
LC_ALL=C sort -z |
|
||||||
|
xargs -0 sha256sum >"$checksum_manifest"
|
||||||
|
)
|
||||||
|
mv "$checksum_manifest" "$bundle_root/SHA256SUMS"
|
||||||
|
(
|
||||||
|
cd "$bundle_root"
|
||||||
|
sha256sum -c SHA256SUMS
|
||||||
|
)
|
||||||
|
|
||||||
|
mkdir -p "$repo_root/dist"
|
||||||
|
zip_path="$repo_root/dist/$bundle_name.zip"
|
||||||
|
checksum_path="$zip_path.sha256"
|
||||||
|
[[ ! -e "$zip_path" && ! -e "$checksum_path" ]] ||
|
||||||
|
die "handoff already exists: $zip_path"
|
||||||
|
|
||||||
|
source_epoch=$(git show -s --format=%ct "$release_tag^{commit}")
|
||||||
|
[[ "$source_epoch" =~ ^[0-9]+$ ]] || die 'tag commit time is not numeric'
|
||||||
|
find "$bundle_root" -exec touch -d "@$source_epoch" {} +
|
||||||
|
(
|
||||||
|
cd "$work_dir"
|
||||||
|
find "$bundle_name" -print | LC_ALL=C sort | zip -X -q "$zip_path" -@
|
||||||
|
)
|
||||||
|
|
||||||
|
unzip -t "$zip_path"
|
||||||
|
bash "$scanner" --archive "$zip_path"
|
||||||
|
|
||||||
|
verify_dir="$work_dir/verified"
|
||||||
|
mkdir -p "$verify_dir"
|
||||||
|
unzip -q "$zip_path" -d "$verify_dir"
|
||||||
|
extracted_root="$verify_dir/$bundle_name"
|
||||||
|
[[ -d "$extracted_root" ]] || die 'validated ZIP did not contain the expected root'
|
||||||
|
(
|
||||||
|
cd "$extracted_root"
|
||||||
|
sha256sum -c SHA256SUMS
|
||||||
|
)
|
||||||
|
bash "$scanner" --tree "$extracted_root"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$repo_root/dist"
|
||||||
|
sha256sum "$(basename "$zip_path")" >"$(basename "$checksum_path")"
|
||||||
|
sha256sum -c "$(basename "$checksum_path")"
|
||||||
|
)
|
||||||
|
|
||||||
|
printf 'PASS: source-only openSUSE handoff created\n'
|
||||||
|
printf 'ZIP: %s\n' "$zip_path"
|
||||||
|
printf 'SHA-256: %s\n' "$checksum_path"
|
||||||
|
printf 'Tag: %s\nCommit: %s\n' "$release_tag" "$tag_commit"
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue