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
|
|
@ -1,56 +1,60 @@
|
|||
# Maintainer: Cristian Cezar Moisés <sac@securityops.co>
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# AUR submission instructions:
|
||||
# 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.
|
||||
# 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
|
||||
# zupt-VERSION.tar.gz alongside the PKGBUILD.
|
||||
# Test locally with `makepkg -s` after the release archive is published.
|
||||
|
||||
pkgname=vaptvupt
|
||||
pkgver=5.0.0
|
||||
pkgname=zupt
|
||||
pkgver=5.2.2
|
||||
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)'
|
||||
arch=('x86_64' 'aarch64')
|
||||
url='https://git.securityops.co/cristiancmoises/vaptvupt'
|
||||
license=('AGPL-3.0-or-later')
|
||||
arch=('x86_64')
|
||||
url='https://github.com/cristiancmoises/zupt'
|
||||
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')
|
||||
makedepends=('gcc')
|
||||
makedepends=('gcc' 'git' 'make')
|
||||
checkdepends=('python')
|
||||
|
||||
# Replace SHA256 placeholder with output of:
|
||||
# sha256sum /tmp/zupt-2.4.4.tar.gz
|
||||
source=("${pkgname}-${pkgver}.tar.gz::https://git.securityops.co/cristiancmoises/vaptvupt/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||
sha256sums=('SKIP')
|
||||
source=("${pkgname}-${pkgver}.tar.gz::https://github.com/cristiancmoises/zupt/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||
# Updated from the byte-reproducible upstream release archive before publishing.
|
||||
sha256sums=('REPLACE_AFTER_FINAL_RELEASE_ARCHIVE_IS_BUILT')
|
||||
|
||||
build() {
|
||||
cd "${pkgname}-${pkgver}"
|
||||
# Source-only build (WITH_SDK=0) with the project's strict warning set.
|
||||
CFLAGS="${CFLAGS:--O2 -std=c11} -Wall -Wextra -Wpedantic" \
|
||||
make WITH_SDK=0 -j"$(nproc)"
|
||||
make WITH_SDK=0 WITH_PQBOX=0 -j"$(nproc)"
|
||||
}
|
||||
|
||||
check() {
|
||||
cd "${pkgname}-${pkgver}"
|
||||
# Distro-safe regression subset (F-06..F-12, dedup-nonce, NIST/RFC vectors).
|
||||
make WITH_SDK=0 check
|
||||
# Distro-safe quick, traversal, integrity, codec and NIST/RFC checks.
|
||||
make WITH_SDK=0 WITH_PQBOX=0 check
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "${pkgname}-${pkgver}"
|
||||
# Source-only build (no vendored libraries); `make install` places the
|
||||
# binary, the zupt symlink, the man pages and the shell completions.
|
||||
make DESTDIR="${pkgdir}" PREFIX=/usr WITH_SDK=0 install
|
||||
# binary, man page and shell completions under the public zupt name.
|
||||
make DESTDIR="${pkgdir}" PREFIX=/usr WITH_SDK=0 WITH_PQBOX=0 \
|
||||
INSTALL_LEGACY_ALIAS=0 install
|
||||
|
||||
# Docs that aren't part of `make install`
|
||||
install -Dm644 README.md "${pkgdir}/usr/share/doc/${pkgname}/README.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 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
|
||||
# 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}"
|
||||
ARCH="${ARCH:-x86_64}"
|
||||
PKGNAME="vaptvupt"
|
||||
LEGACY="zupt"
|
||||
NAME="$PKGNAME-$VERSION-$ARCH"
|
||||
OUT="/tmp/${NAME}.AppDir"
|
||||
set -Eeuo pipefail
|
||||
|
||||
rm -rf "$OUT"
|
||||
mkdir -p "$OUT/usr/bin" "$OUT/usr/share/applications" "$OUT/usr/share/icons/hicolor/256x256/apps"
|
||||
umask 022
|
||||
export LC_ALL=C
|
||||
|
||||
# Source-only build: the binary links only libc/libm/pthread from the host,
|
||||
# so the AppDir ships no bundled libraries.
|
||||
install -m 755 $PKGNAME "$OUT/usr/bin/$PKGNAME"
|
||||
ln -sf $PKGNAME "$OUT/usr/bin/$LEGACY"
|
||||
die() {
|
||||
printf 'FAIL: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cat > "$OUT/AppRun" <<APPRUN
|
||||
#!/bin/bash
|
||||
HERE="\$(dirname "\$(readlink -f "\${0}")")"
|
||||
export PATH="\$HERE/usr/bin:\$PATH"
|
||||
exec "\$HERE/usr/bin/$PKGNAME" "\$@"
|
||||
APPRUN
|
||||
chmod +x "$OUT/AppRun"
|
||||
[[ $(uname -s) == Linux ]] || die 'AppImage packages must be built on Linux'
|
||||
|
||||
cat > "$OUT/$PKGNAME.desktop" <<DESK
|
||||
[Desktop Entry]
|
||||
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/"
|
||||
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||
cd -- "$repo_root"
|
||||
|
||||
# 1x1 PNG placeholder — replace with a real icon when the brand asset exists
|
||||
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"
|
||||
cp "$OUT/$PKGNAME.png" "$OUT/usr/share/icons/hicolor/256x256/apps/$PKGNAME.png"
|
||||
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"
|
||||
|
||||
if command -v appimagetool >/dev/null 2>&1; then
|
||||
ARCH=$ARCH appimagetool "$OUT" "/tmp/${NAME}.AppImage" 2>&1 | tail -5
|
||||
echo "Built: /tmp/${NAME}.AppImage"
|
||||
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"
|
||||
|
||||
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
|
||||
|
||||
# Always produce the AppDir tarball as well -- some environments (no FUSE,
|
||||
# strict execve policies, etc.) cannot run the .AppImage directly. The
|
||||
# tarball is the universal fallback: extract and run AppRun.
|
||||
cd /tmp
|
||||
tar -czf "${NAME}.AppDir.tar.gz" "$(basename "$OUT")"
|
||||
echo "Built: /tmp/${NAME}.AppDir.tar.gz"
|
||||
echo "Users can run: tar xzf ${NAME}.AppDir.tar.gz && ./${NAME}.AppDir/AppRun version"
|
||||
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')}
|
||||
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-appimage.XXXXXXXX")
|
||||
appdir=$work/ZUPT.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
|
||||
|
||||
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
|
||||
# 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
|
||||
cd "$(dirname "$0")/.."
|
||||
set -Eeuo pipefail
|
||||
|
||||
VERSION="${VERSION:-3.0.0}"
|
||||
ARCH="${ARCH:-amd64}"
|
||||
PKGNAME="vaptvupt"
|
||||
LEGACY="zupt"
|
||||
umask 022
|
||||
export LC_ALL=C
|
||||
|
||||
PKG="${PKGNAME}_${VERSION}_${ARCH}"
|
||||
ROOT="/tmp/$PKG"
|
||||
|
||||
# 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
|
||||
die() {
|
||||
printf 'FAIL: %s\n' "$*" >&2
|
||||
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
|
||||
|
||||
echo "[deb] Building vaptvupt"
|
||||
make clean >/dev/null 2>&1 || true
|
||||
make -j"$(nproc)" >/dev/null
|
||||
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')}
|
||||
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-deb.XXXXXXXX")
|
||||
stage=$work/stage
|
||||
extract=$work/extract
|
||||
|
||||
echo "[deb] Patching rpath -> /usr/lib/$PKGNAME:/usr/lib64/$PKGNAME"
|
||||
patchelf --set-rpath "/usr/lib/$PKGNAME:/usr/lib64/$PKGNAME" $PKGNAME
|
||||
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
|
||||
|
||||
if ! readelf -d $PKGNAME | grep -q "RUNPATH.*\[/usr/lib/$PKGNAME:/usr/lib64/$PKGNAME\]"; then
|
||||
echo "ERROR: built $PKGNAME does not have correct RUNPATH" >&2
|
||||
readelf -d $PKGNAME | grep -E "RPATH|RUNPATH"
|
||||
exit 1
|
||||
printf '[deb] 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
|
||||
|
||||
rm -rf "$ROOT"
|
||||
mkdir -p "$ROOT/DEBIAN" \
|
||||
"$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"
|
||||
make DESTDIR="$stage" PREFIX=/usr WITH_SDK=0 WITH_PQBOX=0 \
|
||||
INSTALL_LEGACY_ALIAS=0 install
|
||||
|
||||
# Binary + legacy symlink
|
||||
install -m 755 $PKGNAME "$ROOT/usr/bin/$PKGNAME"
|
||||
ln -sf $PKGNAME "$ROOT/usr/bin/$LEGACY"
|
||||
binary=$stage/usr/bin/zupt
|
||||
[[ -x $binary ]] || die 'staged /usr/bin/zupt is missing'
|
||||
[[ ! -e $stage/usr/bin/vaptvupt ]] || die 'legacy /usr/bin/vaptvupt must not be packaged'
|
||||
|
||||
# Bundled libzuptsdk
|
||||
install -m 755 "$SDK_LIB" "$ROOT/usr/lib/$PKGNAME/libzuptsdk.so.2.0.0"
|
||||
ln -sf libzuptsdk.so.2.0.0 "$ROOT/usr/lib/$PKGNAME/libzuptsdk.so.2"
|
||||
ln -sf libzuptsdk.so.2.0.0 "$ROOT/usr/lib/$PKGNAME/libzuptsdk.so"
|
||||
install -m 755 "$PQVV_LIB" "$ROOT/usr/lib/$PKGNAME/libpqvaptvupt.so.0.6.0"
|
||||
ln -sf libpqvaptvupt.so.0.6.0 "$ROOT/usr/lib/$PKGNAME/libpqvaptvupt.so.0"
|
||||
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"
|
||||
if readelf -d "$binary" 2>/dev/null | grep -Eq '(RPATH|RUNPATH)'; then
|
||||
readelf -d "$binary" | grep -E '(RPATH|RUNPATH)' >&2
|
||||
die 'staged executable contains RPATH/RUNPATH'
|
||||
fi
|
||||
if readelf -d "$binary" 2>/dev/null | grep -Eqi '(vendor/|libvuptsdk|libpqvaptvupt)'; then
|
||||
die 'staged executable references a vendored optional library'
|
||||
fi
|
||||
|
||||
# Shell completions
|
||||
if [ -f completions/vaptvupt.bash ]; then
|
||||
install -m 0644 completions/vaptvupt.bash "$ROOT/usr/share/bash-completion/completions/$PKGNAME"
|
||||
ln -sf $PKGNAME "$ROOT/usr/share/bash-completion/completions/$LEGACY"
|
||||
fi
|
||||
if [ -f completions/_vaptvupt ]; then
|
||||
install -m 0644 completions/_vaptvupt "$ROOT/usr/share/zsh/site-functions/_$PKGNAME"
|
||||
ln -sf _$PKGNAME "$ROOT/usr/share/zsh/site-functions/_$LEGACY"
|
||||
fi
|
||||
if [ -f completions/vaptvupt.fish ]; then
|
||||
install -m 0644 completions/vaptvupt.fish "$ROOT/usr/share/fish/vendor_completions.d/$PKGNAME.fish"
|
||||
forbidden=$(find "$stage" -type f \( \
|
||||
-name '*.o' -o -name '*.obj' -o -name '*.a' -o -name '*.so' -o \
|
||||
-name '*.so.*' -o -name '*.dll' -o -name '*.dylib' -o -name '*.exe' \
|
||||
\) -print)
|
||||
[[ -z $forbidden ]] || {
|
||||
printf '%s\n' "$forbidden" >&2
|
||||
die 'compiled library or object found in package staging tree'
|
||||
}
|
||||
|
||||
docdir=$stage/usr/share/doc/zupt
|
||||
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
|
||||
|
||||
# Docs
|
||||
install -m 0644 README.md "$ROOT/usr/share/doc/$PKGNAME/README.md"
|
||||
install -m 0644 LICENSE "$ROOT/usr/share/doc/$PKGNAME/copyright"
|
||||
[ -f SECURITY.md ] && install -m 0644 SECURITY.md "$ROOT/usr/share/doc/$PKGNAME/SECURITY.md"
|
||||
[ -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
|
||||
installed_kib=$(du -sk "$stage/usr" | awk '{print $1}')
|
||||
cat > "$stage/DEBIAN/control" <<EOF
|
||||
Package: zupt
|
||||
Version: $version
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: $ARCH
|
||||
Provides: $LEGACY (= $VERSION)
|
||||
Replaces: $LEGACY (<< 3.0.0)
|
||||
Conflicts: $LEGACY (<< 3.0.0)
|
||||
Depends: libargon2-1, libssl3 | libssl3t64
|
||||
Installed-Size: $INSTALLED_KB
|
||||
Architecture: $arch
|
||||
Depends: $depends
|
||||
Installed-Size: $installed_kib
|
||||
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
|
||||
Homepage: https://git.securityops.co/cristiancmoises/zupt
|
||||
Description: Post-quantum backup compression utility (formerly zupt)
|
||||
VaptVupt (renamed from Zupt in v3.0.0 due to a prior INPI Brasil
|
||||
trademark on the name) is a pure-C11 backup compression utility
|
||||
featuring post-quantum hybrid encryption (ML-KEM-768 + X25519,
|
||||
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.
|
||||
Homepage: https://github.com/cristiancmoises/zupt
|
||||
Description: Backup compression with authenticated and post-quantum encryption
|
||||
ZUPT creates compressed backup archives with optional password encryption
|
||||
or ML-KEM-768 and X25519 hybrid key encapsulation. This package is built from
|
||||
source with the optional libvuptsdk and libpqvaptvupt integrations disabled.
|
||||
EOF
|
||||
|
||||
DEB_OUT="/tmp/${PKGNAME}_${VERSION}_${ARCH}.deb"
|
||||
dpkg-deb --build --root-owner-group "$ROOT" "$DEB_OUT" >/dev/null
|
||||
echo "Built: $DEB_OUT ($(du -h "$DEB_OUT" | cut -f1))"
|
||||
dpkg-deb -I "$DEB_OUT" | sed -n '1,20p'
|
||||
package_tmp=$work/$(basename -- "$output")
|
||||
dpkg-deb --build --root-owner-group "$stage" "$package_tmp" >/dev/null
|
||||
dpkg-deb --info "$package_tmp" >/dev/null
|
||||
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
|
||||
# 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
|
||||
cd "$(dirname "$0")/.."
|
||||
set -Eeuo pipefail
|
||||
|
||||
VERSION="${VERSION:-2.4.7}"
|
||||
ARCH="${ARCH:-$(uname -m)}" # x86_64 or arm64
|
||||
NAME="Zupt-${VERSION}-${ARCH}"
|
||||
STAGE="/tmp/${NAME}.app/Contents"
|
||||
umask 022
|
||||
export LC_ALL=C
|
||||
|
||||
# ── Platform check ──
|
||||
if [ "$(uname)" != "Darwin" ]; then
|
||||
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
|
||||
die() {
|
||||
printf 'FAIL: %s\n' "$*" >&2
|
||||
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
|
||||
|
||||
# ── Build zupt (universal binary if possible) ──
|
||||
echo "[dmg] Building zupt"
|
||||
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=$(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
|
||||
if xcrun --sdk macosx clang -dM -E - </dev/null | grep -q __aarch64__; then
|
||||
# arm64 host → can cross-build for x86_64 via -arch flag
|
||||
CFLAGS="-O2 -std=c11 -arch arm64 -arch x86_64" \
|
||||
LDFLAGS="-arch arm64 -arch x86_64" \
|
||||
make -j"$(sysctl -n hw.ncpu)" || make -j"$(sysctl -n hw.ncpu)"
|
||||
else
|
||||
make -j"$(sysctl -n hw.ncpu)"
|
||||
make -j"$jobs" CC=clang V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0
|
||||
if [[ $run_checks == 1 ]]; then
|
||||
make CC=clang V=1 WITH_SDK=0 WITH_PQBOX=0 INSTALL_LEGACY_ALIAS=0 check
|
||||
fi
|
||||
test_macos_binary "$repo_root/zupt"
|
||||
|
||||
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
|
||||
|
||||
# ── Stage the .app bundle ──
|
||||
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
|
||||
cat > "$contents/Info.plist" <<EOF
|
||||
<?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">
|
||||
<dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>co.securityops.zupt</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Zupt</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Zupt</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<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>
|
||||
<key>CFBundleIdentifier</key><string>dev.zupt.cli</string>
|
||||
<key>CFBundleName</key><string>ZUPT</string>
|
||||
<key>CFBundleDisplayName</key><string>ZUPT</string>
|
||||
<key>CFBundleExecutable</key><string>zupt</string>
|
||||
<key>CFBundlePackageType</key><string>APPL</string>
|
||||
<key>CFBundleVersion</key><string>$version</string>
|
||||
<key>CFBundleShortVersionString</key><string>$version</string>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
EOF
|
||||
plutil -lint "$contents/Info.plist"
|
||||
|
||||
cp README.md "$STAGE/Resources/" 2>/dev/null || true
|
||||
cp LICENSE "$STAGE/Resources/" 2>/dev/null || true
|
||||
|
||||
# ── 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
|
||||
if [[ -n ${CODESIGN_IDENTITY:-} ]]; then
|
||||
codesign --force --options runtime --timestamp --sign "$CODESIGN_IDENTITY" "$app"
|
||||
codesign --verify --deep --strict "$app"
|
||||
fi
|
||||
|
||||
# ── Build .dmg ──
|
||||
echo "[dmg] Building disk image"
|
||||
DMG="/tmp/${NAME}.dmg"
|
||||
rm -f "$DMG"
|
||||
|
||||
# Stage a directory tree that becomes the .dmg root
|
||||
DMGSRC="/tmp/${NAME}-dmgsrc"
|
||||
rm -rf "$DMGSRC"
|
||||
mkdir -p "$DMGSRC"
|
||||
cp -R "/tmp/${NAME}.app" "$DMGSRC/Zupt.app"
|
||||
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"
|
||||
cp -R "$app" "$dmg_root/ZUPT.app"
|
||||
cat > "$dmg_root/Install ZUPT.command" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
installer_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
source_binary=$installer_dir/ZUPT.app/Contents/MacOS/zupt
|
||||
target_dir=/usr/local/bin
|
||||
if [[ ! -d $target_dir || ! -w $target_dir ]]; then
|
||||
target_dir=${XDG_BIN_HOME:-$HOME/.local/bin}
|
||||
mkdir -p "$target_dir"
|
||||
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 ""
|
||||
echo "Built: $DMG ($(du -h "$DMG" | cut -f1))"
|
||||
echo "Users mount and drag 'Zupt.app' or double-click 'Install Zupt.command'."
|
||||
hdiutil create -fs HFS+ -srcfolder "$dmg_root" -volname "ZUPT $version" \
|
||||
-format UDZO -ov "$dmg_tmp"
|
||||
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
|
||||
# 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}"
|
||||
APPDIR="/tmp/vaptvupt-gui.AppDir"
|
||||
# Build a dependency-light GUI AppImage. The ZUPT CLI is compiled from this
|
||||
# tree and bundled; Python 3 plus PySide6 or PyQt6 remain host requirements.
|
||||
|
||||
rm -rf "$APPDIR"
|
||||
mkdir -p "$APPDIR/usr/bin" \
|
||||
"$APPDIR/usr/lib/vaptvupt-gui" \
|
||||
"$APPDIR/usr/share/applications" \
|
||||
"$APPDIR/usr/share/icons/hicolor/256x256/apps"
|
||||
set -Eeuo pipefail
|
||||
umask 022
|
||||
export LC_ALL=C
|
||||
|
||||
# Python source
|
||||
install -m 644 gui/src/zupt_gui.py "$APPDIR/usr/lib/vaptvupt-gui/"
|
||||
die() { printf 'FAIL: %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
# Wrapper
|
||||
cat > "$APPDIR/usr/bin/vaptvupt-gui" <<'WRAP'
|
||||
[[ $(uname -s) == Linux ]] || die 'AppImage packages must be built on Linux'
|
||||
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
|
||||
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
|
||||
chmod 755 "$APPDIR/usr/bin/vaptvupt-gui"
|
||||
|
||||
# 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'
|
||||
chmod 0755 "$appdir/usr/bin/zupt-gui"
|
||||
cat >"$appdir/AppRun" <<'APPRUN'
|
||||
#!/bin/sh
|
||||
HERE="$(dirname "$(readlink -f "$0")")"
|
||||
export PATH="$HERE/usr/bin:$PATH"
|
||||
|
||||
# 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" "$@"
|
||||
appdir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
|
||||
exec "$appdir/usr/bin/zupt-gui" "$@"
|
||||
APPRUN
|
||||
chmod 755 "$APPDIR/AppRun"
|
||||
chmod 0755 "$appdir/AppRun"
|
||||
cp -- "$appdir/usr/share/applications/zupt-gui.desktop" "$appdir/"
|
||||
|
||||
# Build AppImage
|
||||
if command -v appimagetool >/dev/null 2>&1; then
|
||||
ARCH=x86_64 appimagetool "$APPDIR" "/tmp/VaptVupt-GUI-$VERSION-x86_64.AppImage" 2>&1 | tail -5
|
||||
echo "Built: /tmp/VaptVupt-GUI-$VERSION-x86_64.AppImage"
|
||||
else
|
||||
cd /tmp
|
||||
rm -f "VaptVupt-GUI-$VERSION-x86_64.AppDir.tar.gz"
|
||||
tar -czf "VaptVupt-GUI-$VERSION-x86_64.AppDir.tar.gz" vaptvupt-gui.AppDir
|
||||
cd - >/dev/null
|
||||
echo "appimagetool unavailable; portable AppDir tarball at:"
|
||||
echo " /tmp/VaptVupt-GUI-$VERSION-x86_64.AppDir.tar.gz"
|
||||
echo "Run via: tar -xzf ... && ./vaptvupt-gui.AppDir/AppRun"
|
||||
echo "Convert to AppImage on a host with appimagetool:"
|
||||
echo " ARCH=x86_64 appimagetool vaptvupt-gui.AppDir VaptVupt-GUI-$VERSION-x86_64.AppImage"
|
||||
fi
|
||||
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/object in AppDir'; }
|
||||
|
||||
QT_QPA_PLATFORM=offscreen "$appdir/AppRun" --version | grep -Fq "zupt-gui $version" || \
|
||||
die 'AppDir GUI/CLI integration check failed'
|
||||
export ARCH=$arch 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'
|
||||
QT_QPA_PLATFORM=offscreen "$image_tmp" --version | grep -Fq "zupt-gui $version" || \
|
||||
die 'generated AppImage execution check failed'
|
||||
|
||||
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
|
||||
# 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}"
|
||||
ARCH="all"
|
||||
PKG="vaptvupt-gui_${VERSION}_${ARCH}"
|
||||
ROOT="/tmp/$PKG"
|
||||
# Build the architecture-independent GUI package from tracked source. The CLI
|
||||
# dependency is built and tested in baseline mode but is packaged separately.
|
||||
|
||||
rm -rf "$ROOT"
|
||||
mkdir -p "$ROOT/DEBIAN" \
|
||||
"$ROOT/usr/bin" \
|
||||
"$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"
|
||||
set -Eeuo pipefail
|
||||
umask 022
|
||||
export LC_ALL=C
|
||||
|
||||
# Source files
|
||||
install -m 644 gui/src/zupt_gui.py "$ROOT/usr/lib/vaptvupt-gui/"
|
||||
die() { printf 'FAIL: %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
# Wrapper script in /usr/bin
|
||||
cat > "$ROOT/usr/bin/vaptvupt-gui" <<'WRAP'
|
||||
#!/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"
|
||||
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||
cd -- "$repo_root"
|
||||
|
||||
# Desktop entry
|
||||
cat > "$ROOT/usr/share/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
|
||||
MimeType=application/x-zupt;
|
||||
Keywords=archive;compression;encryption;post-quantum;backup;
|
||||
DESKTOP
|
||||
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'"
|
||||
|
||||
# Man page
|
||||
if [ -f doc/vaptvupt-gui.1 ]; then
|
||||
install -m 644 doc/vaptvupt-gui.1 "$ROOT/usr/share/man/man1/vaptvupt-gui.1"
|
||||
gzip -9n "$ROOT/usr/share/man/man1/vaptvupt-gui.1"
|
||||
fi
|
||||
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}_all.deb
|
||||
[[ ! -e $output ]] || die "refusing to overwrite existing output: $output"
|
||||
|
||||
# Icon
|
||||
if [ -f gui/assets/zupt-icon.png ]; then
|
||||
cp gui/assets/zupt-icon.png "$ROOT/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('$ROOT/usr/share/icons/hicolor/256x256/apps/zupt-gui.png','wb').write(png(256, 256, (88, 92, 215)))
|
||||
"
|
||||
fi
|
||||
for command_name in make python3 dpkg-deb gzip sha256sum; do
|
||||
command -v -- "$command_name" >/dev/null 2>&1 || \
|
||||
die "required command not found: $command_name"
|
||||
done
|
||||
|
||||
# Docs
|
||||
install -m 644 gui/README.md "$ROOT/usr/share/doc/vaptvupt-gui/" 2>/dev/null || true
|
||||
gzip -9n -c CHANGELOG.md > "$ROOT/usr/share/doc/vaptvupt-gui/changelog.gz"
|
||||
jobs=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')}
|
||||
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-gui-deb.XXXXXXXX")
|
||||
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'
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: vaptvupt-gui
|
||||
Upstream-Contact: Cristian Cezar Moisés <zupt@riseup.net>
|
||||
Source: https://git.securityops.co/cristiancmoises/zupt
|
||||
printf '[GUI deb] 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'
|
||||
|
||||
Files: *
|
||||
Copyright: 2025-2026 Cristian Cezar Moisés
|
||||
License: AGPL-3.0+
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of 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.
|
||||
.
|
||||
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
|
||||
PYTHONDONTWRITEBYTECODE=1 python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
source = Path("gui/src/zupt_gui.py").read_text(encoding="utf-8")
|
||||
compile(source, "gui/src/zupt_gui.py", "exec")
|
||||
PY
|
||||
|
||||
# Control
|
||||
INSTALLED_SIZE=$(du -sk "$ROOT" | cut -f1)
|
||||
cat > "$ROOT/DEBIAN/control" <<EOF
|
||||
Package: vaptvupt-gui
|
||||
Version: $VERSION
|
||||
mkdir -p -- "$stage"
|
||||
bash gui/install.sh --destdir "$stage" --prefix /usr
|
||||
[[ ! -e $stage/usr/bin/vaptvupt-gui ]] || die 'legacy vaptvupt-gui alias must not be packaged'
|
||||
|
||||
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
|
||||
Priority: optional
|
||||
Architecture: $ARCH
|
||||
Depends: python3 (>= 3.9), python3-pyqt6 | python3-pyside6, vaptvupt (>= 3.0.0) | zupt (>= 2.2.3)
|
||||
Provides: zupt-gui (= ${VERSION})
|
||||
Replaces: zupt-gui (<< 1.2.0)
|
||||
Conflicts: zupt-gui (<< 1.2.0)
|
||||
Maintainer: Cristian Cezar Moisés <zupt@riseup.net>
|
||||
Installed-Size: $INSTALLED_SIZE
|
||||
Homepage: https://git.securityops.co/cristiancmoises/zupt
|
||||
Description: Graphical interface for VaptVupt post-quantum backup utility
|
||||
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.
|
||||
Architecture: all
|
||||
Depends: python3 (>= 3.9), python3-pyqt6 | python3-pyside6.qtwidgets, zupt (= $version)
|
||||
Installed-Size: $installed_kib
|
||||
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
|
||||
Homepage: https://github.com/cristiancmoises/zupt
|
||||
Description: Qt graphical interface for the ZUPT backup utility
|
||||
The GUI creates, inspects, verifies, and extracts .zupt archives through the
|
||||
separately packaged zupt command. Optional SDK and PQ-box controls are
|
||||
shown only when the installed command reports those integrations enabled.
|
||||
EOF
|
||||
|
||||
# Postinst: refresh icon cache + desktop database, print first-run guidance
|
||||
cat > "$ROOT/DEBIAN/postinst" <<'POSTINST'
|
||||
#!/bin/sh
|
||||
set -e
|
||||
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
|
||||
forbidden=$(find "$stage" -type f \( \
|
||||
-name '*.o' -o -name '*.obj' -o -name '*.a' -o -name '*.so' -o \
|
||||
-name '*.so.*' -o -name '*.dll' -o -name '*.dylib' -o -name '*.exe' \
|
||||
\) -print)
|
||||
[[ -z $forbidden ]] || { printf '%s\n' "$forbidden" >&2; die 'compiled artifact in GUI package'; }
|
||||
|
||||
package_tmp=$work/$(basename -- "$output")
|
||||
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_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
|
||||
|
||||
# Friendly first-run check: warn the user if no Qt6 binding is installed.
|
||||
# We don't fail the install (deb deps already enforce this); we just print
|
||||
# clear guidance for users who saw "unmet dependencies" earlier.
|
||||
if ! python3 -c 'import PyQt6.QtWidgets' 2>/dev/null \
|
||||
&& ! python3 -c 'import PySide6.QtWidgets' 2>/dev/null; then
|
||||
cat << 'MSG'
|
||||
mkdir -p -- "$extract"
|
||||
dpkg-deb --extract "$package_tmp" "$extract"
|
||||
PYTHONDONTWRITEBYTECODE=1 python3 - <<PY
|
||||
from pathlib import Path
|
||||
p = Path("$extract/usr/lib/zupt-gui/zupt_gui.py")
|
||||
compile(p.read_text(encoding="utf-8"), str(p), "exec")
|
||||
PY
|
||||
|
||||
──────────────────────────────────────────────────────────────────────
|
||||
vaptvupt-gui installed, but no Qt6 Python binding is available.
|
||||
|
||||
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
|
||||
mv -- "$package_tmp" "$output"
|
||||
sha256sum "$output"
|
||||
printf 'PASS: built and content-validated %s\n' "$output"
|
||||
|
|
|
|||
|
|
@ -1,151 +1,146 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# 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}"
|
||||
RPMROOT="/tmp/rpmbuild-vaptvupt-gui"
|
||||
rm -rf "$RPMROOT"
|
||||
mkdir -p "$RPMROOT"/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
|
||||
# Build a real noarch RPM and source RPM. Run this in a native RPM build
|
||||
# environment; there is deliberately no --nodeps or tarball fallback.
|
||||
|
||||
TMP="/tmp/vaptvupt-gui-$VERSION"
|
||||
rm -rf "$TMP" && mkdir -p "$TMP/src" "$TMP/doc" "$TMP/assets"
|
||||
cp gui/src/zupt_gui.py "$TMP/src/"
|
||||
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"
|
||||
set -Eeuo pipefail
|
||||
umask 022
|
||||
export LC_ALL=C
|
||||
|
||||
cat > "$RPMROOT/SPECS/vaptvupt-gui.spec" <<EOF
|
||||
Name: vaptvupt-gui
|
||||
Version: $VERSION
|
||||
Release: 1%{?dist}
|
||||
Summary: Graphical interface for VaptVupt post-quantum backup utility (formerly zupt-gui)
|
||||
die() { printf 'FAIL: %s\n' "$*" >&2; 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}
|
||||
[[ $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
|
||||
URL: https://git.securityops.co/cristiancmoises/zupt
|
||||
Source0: vaptvupt-gui-%{version}.tar.gz
|
||||
URL: https://github.com/cristiancmoises/zupt
|
||||
Source0: %{name}-%{version}.tar.gz
|
||||
BuildArch: noarch
|
||||
|
||||
BuildRequires: python3 >= 3.9
|
||||
Requires: python3 >= 3.9
|
||||
Requires: (python3-qt6 or python3-pyside6 or python3-pyqt6)
|
||||
Requires: (vaptvupt >= 3.0.0 or zupt >= 2.2.3)
|
||||
Provides: zupt-gui = %{version}-%{release}
|
||||
Obsoletes: zupt-gui < 1.2.0
|
||||
Conflicts: zupt-gui < 1.2.0
|
||||
Requires: zupt >= %{version}
|
||||
|
||||
%description
|
||||
PySide6/PyQt6 frontend for VaptVupt (renamed from 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. Auto-detects whichever Qt6 binding is
|
||||
installed at startup.
|
||||
ZUPT GUI creates, inspects, verifies, and extracts .zupt archives through
|
||||
the separately packaged zupt command. Optional SDK and PQ-box controls are
|
||||
shown only when that command reports the corresponding integration enabled.
|
||||
|
||||
%prep
|
||||
%autosetup
|
||||
|
||||
%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 -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}%{_libdir}/vaptvupt-gui
|
||||
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'
|
||||
cat >%{buildroot}%{_bindir}/zupt-gui <<'WRAP'
|
||||
#!/bin/sh
|
||||
exec python3 %{_libdir}/vaptvupt-gui/zupt_gui.py "\$@"
|
||||
exec python3 %{_datadir}/zupt-gui/zupt_gui.py "\$@"
|
||||
WRAP
|
||||
chmod 755 %{buildroot}%{_bindir}/vaptvupt-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
|
||||
chmod 0755 %{buildroot}%{_bindir}/zupt-gui
|
||||
|
||||
%files
|
||||
%doc README.md
|
||||
%license LICENSE
|
||||
%{_bindir}/vaptvupt-gui
|
||||
%license LICENSE LICENSE-AGPL-3.0 LICENSE-GUI
|
||||
%doc README.md ASSET-PROVENANCE.md
|
||||
%{_bindir}/zupt-gui
|
||||
%{_libdir}/vaptvupt-gui/zupt_gui.py
|
||||
%{_datadir}/applications/vaptvupt-gui.desktop
|
||||
%{_datadir}/icons/hicolor/256x256/apps/vaptvupt-gui.png
|
||||
%{_datadir}/zupt-gui/zupt_gui.py
|
||||
%{_datadir}/applications/zupt-gui.desktop
|
||||
%{_datadir}/icons/hicolor/256x256/apps/zupt-gui.png
|
||||
%{_mandir}/man1/zupt-gui.1*
|
||||
|
||||
%changelog
|
||||
* Sun May 25 2026 Cristian Cezar Moisés <zupt@riseup.net> - $VERSION-1
|
||||
- v1.2.0: package renamed zupt-gui → vaptvupt-gui (parent CLI also
|
||||
renamed; INPI Brasil trademark on "Zupt"). Legacy /usr/bin/zupt-gui
|
||||
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
|
||||
* Mon Aug 31 2026 Cristian Cezar Moisés <sac@securityops.co> - $version-1
|
||||
- Package the integrated GUI under its restored ZUPT identity.
|
||||
- Require the separately built source-only baseline CLI package.
|
||||
EOF
|
||||
|
||||
if command -v rpmbuild >/dev/null 2>&1; then
|
||||
# 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
|
||||
# since the runtime check on the target system is what actually
|
||||
# matters. The Requires: lines still apply on install.
|
||||
rpmbuild --define "_topdir $RPMROOT" --nodeps -bb "$RPMROOT/SPECS/vaptvupt-gui.spec" 2>&1 | tail -3
|
||||
if [ -f "$RPMROOT/RPMS/noarch/vaptvupt-gui-${VERSION}-1.noarch.rpm" ]; then
|
||||
cp "$RPMROOT/RPMS/noarch/vaptvupt-gui-${VERSION}-1.noarch.rpm" \
|
||||
"/tmp/vaptvupt-gui-${VERSION}-1.noarch.rpm"
|
||||
echo "Built: /tmp/vaptvupt-gui-${VERSION}-1.noarch.rpm"
|
||||
fi
|
||||
cp "$RPMROOT/RPMS/noarch/zupt-gui-$VERSION-1."*.rpm /tmp/ 2>/dev/null || true
|
||||
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"
|
||||
rpmbuild --define "_topdir $top" -ba "$top/SPECS/zupt-gui.spec"
|
||||
|
||||
mapfile -t main_rpms < <(find "$top/RPMS" -type f -name "zupt-gui-$version-*.noarch.rpm" -print | sort)
|
||||
mapfile -t source_rpms < <(find "$top/SRPMS" -type f -name "zupt-gui-$version-*.src.rpm" -print | sort)
|
||||
[[ ${#main_rpms[@]} -eq 1 ]] || die "expected one GUI RPM, found ${#main_rpms[@]}"
|
||||
[[ ${#source_rpms[@]} -eq 1 ]] || die "expected one GUI source RPM, found ${#source_rpms[@]}"
|
||||
|
||||
rpm -qpl "${main_rpms[0]}" >"$work/contents.txt"
|
||||
grep -q '^/usr/bin/zupt-gui$' "$work/contents.txt" || die 'GUI launcher missing from RPM'
|
||||
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 GUI RPM'
|
||||
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
|
||||
# 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
|
||||
cd "$(dirname "$0")/.."
|
||||
set -Eeuo pipefail
|
||||
|
||||
VERSION="${VERSION:-3.0.0}"
|
||||
ARCH="${ARCH:-x86_64}"
|
||||
RELEASE="1"
|
||||
PKGNAME="vaptvupt"
|
||||
LEGACY="zupt"
|
||||
umask 022
|
||||
export LC_ALL=C
|
||||
|
||||
SDK_LIB="vendor/zuptsdk/libzuptsdk.so.2.0.0"
|
||||
if [ ! -f "$SDK_LIB" ]; then
|
||||
echo "ERROR: $SDK_LIB not found." >&2
|
||||
die() {
|
||||
printf 'FAIL: %s\n' "$*" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "[rpm] Building $PKGNAME"
|
||||
make clean >/dev/null 2>&1 || true
|
||||
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
|
||||
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||
cd -- "$repo_root"
|
||||
|
||||
if ! command -v rpmbuild >/dev/null 2>&1; then
|
||||
echo "[rpm] rpmbuild not found; install rpm package to proceed"
|
||||
exit 1
|
||||
fi
|
||||
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"
|
||||
|
||||
RPMROOT="/tmp/rpmbuild-$PKGNAME"
|
||||
rm -rf "$RPMROOT"
|
||||
mkdir -p "$RPMROOT"/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
|
||||
spec=packaging/opensuse/zupt.spec
|
||||
[[ -f $spec ]] || die "spec file not found: $spec"
|
||||
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}"
|
||||
rm -rf "$STAGE"
|
||||
mkdir -p "$STAGE/$PKGNAME-${VERSION}/completions"
|
||||
dist_dir=${DIST_DIR:-${TMPDIR:-/tmp}/zupt-release}
|
||||
mkdir -p -- "$dist_dir"
|
||||
dist_dir=$(cd -- "$dist_dir" && pwd -P)
|
||||
|
||||
cp $PKGNAME "$STAGE/$PKGNAME-${VERSION}/$PKGNAME"
|
||||
cp "$SDK_LIB" "$STAGE/$PKGNAME-${VERSION}/libzuptsdk.so.2.0.0"
|
||||
cp "vendor/pqvaptvupt/libpqvaptvupt.so.0.6.0" "$STAGE/$PKGNAME-${VERSION}/libpqvaptvupt.so.0.6.0"
|
||||
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}"
|
||||
for command_name in make git rpmbuild rpm rpm2cpio cpio date readelf sha256sum tar; do
|
||||
command -v -- "$command_name" >/dev/null 2>&1 || die "required command not found: $command_name"
|
||||
done
|
||||
|
||||
cat > "$RPMROOT/SPECS/$PKGNAME.spec" <<EOF
|
||||
Name: $PKGNAME
|
||||
Version: $VERSION
|
||||
Release: ${RELEASE}%{?dist}
|
||||
Summary: Post-quantum backup compression utility (formerly zupt)
|
||||
License: AGPL-3.0-or-later AND GPL-3.0-or-later
|
||||
URL: https://git.securityops.co/cristiancmoises/zupt
|
||||
Source0: $PKGNAME-%{version}.tar.gz
|
||||
work=$(mktemp -d "${TMPDIR:-/tmp}/zupt-rpm.XXXXXXXX")
|
||||
top=$work/rpmbuild
|
||||
extract=$work/extract
|
||||
mkdir -p -- "$top/BUILD" "$top/BUILDROOT" "$top/RPMS" "$top/SOURCES" \
|
||||
"$top/SPECS" "$top/SRPMS" "$extract"
|
||||
|
||||
# v3.0.0 rename — INPI Brasil trademark on the prior name "Zupt".
|
||||
# Cleanly supersede legacy 'zupt' RPMs.
|
||||
Provides: $LEGACY = %{version}-%{release}
|
||||
Obsoletes: $LEGACY < 3.0.0
|
||||
Conflicts: $LEGACY < 3.0.0
|
||||
cleanup() {
|
||||
chmod -R u+rwX "$work" 2>/dev/null || true
|
||||
rm -rf -- "$work"
|
||||
}
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
Requires: libargon2
|
||||
Requires: openssl-libs >= 3.0
|
||||
AutoReqProv: no
|
||||
source_tar=$top/SOURCES/zupt-${version}.tar.gz
|
||||
printf '[rpm] creating audited source archive for ZUPT %s\n' "$version"
|
||||
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}
|
||||
%global __os_install_post %{nil}
|
||||
%global _build_id_links none
|
||||
install -m 0644 "$spec" "$top/SPECS/zupt.spec"
|
||||
# OBS converts zupt.changes into RPM changelog metadata. Standalone
|
||||
# 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
|
||||
VaptVupt (renamed from Zupt in v3.0.0 due to INPI Brasil trademark
|
||||
on the prior name) is a backup-oriented compression utility with
|
||||
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.
|
||||
* $changelog_date Cristian Cezar Moisés <sac@securityops.co> - $version-0
|
||||
- Build the release package from audited source with optional SDK and PQBOX
|
||||
features disabled.
|
||||
EOF
|
||||
rpmbuild --define "_topdir $top" -ba "$top/SPECS/zupt.spec"
|
||||
|
||||
rpmbuild --define "_topdir $RPMROOT" \
|
||||
--define "_binary_payload w2.gzdio" \
|
||||
-bb "$RPMROOT/SPECS/$PKGNAME.spec" 2>&1 | tail -5
|
||||
mapfile -t main_rpms < <(find "$top/RPMS" -type f -name "zupt-${version}-*.rpm" \
|
||||
! -name '*-debuginfo-*' ! -name '*-debugsource-*' -print | sort)
|
||||
[[ ${#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)
|
||||
if [ -n "$RPM_PATH" ]; then
|
||||
cp "$RPM_PATH" "/tmp/$PKGNAME-${VERSION}-${RELEASE}.${ARCH}.rpm"
|
||||
echo ""
|
||||
echo "Built: /tmp/$PKGNAME-${VERSION}-${RELEASE}.${ARCH}.rpm ($(du -h "/tmp/$PKGNAME-${VERSION}-${RELEASE}.${ARCH}.rpm" | cut -f1))"
|
||||
rpm -qpi "/tmp/$PKGNAME-${VERSION}-${RELEASE}.${ARCH}.rpm" 2>&1 | head -15
|
||||
mapfile -t source_rpms < <(find "$top/SRPMS" -type f -name "zupt-${version}-*.src.rpm" -print | sort)
|
||||
[[ ${#source_rpms[@]} -eq 1 ]] || die "expected one source RPM, found ${#source_rpms[@]}"
|
||||
source_rpm=${source_rpms[0]}
|
||||
|
||||
rpm -qpi "$main_rpm" >/dev/null
|
||||
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
|
||||
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
|
||||
|
||||
* 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
|
||||
|
||||
* GUI license cleanup: removed MIT-license credit line from the
|
||||
about panel (the GUI is AGPL-3.0-or-later with commercial dual-
|
||||
licensing; the MIT reference was a templating mistake). Replaced
|
||||
gui/LICENSE-GUI (was MIT) with AGPL-3.0-or-later, mirroring the
|
||||
top-level LICENSE. Top-level LICENSE preamble updated to reflect
|
||||
the v3.0.0 Zupt → VaptVupt rename.
|
||||
* GUI license metadata changed to AGPL-3.0-or-later for the then-current
|
||||
source. The original entry incorrectly called earlier MIT notices a
|
||||
templating mistake; the 5.2.2 erratum records that historical grants remain
|
||||
valid for the exact material distributed under them.
|
||||
* GUI version-string parsing bug fix: the v3.0.0 GUI used
|
||||
`replace("zupt ", "")` to peel the product name out of the CLI's
|
||||
version banner, but that substring also appears inside the v3.0.0
|
||||
|
|
|
|||
|
|
@ -1,41 +1,49 @@
|
|||
Source: vaptvupt
|
||||
Source: zupt
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Maintainer: Cristian Cezar Moisés <sac@securityops.co>
|
||||
Build-Depends:
|
||||
bash,
|
||||
coreutils,
|
||||
debhelper-compat (= 13),
|
||||
diffutils,
|
||||
file,
|
||||
findutils,
|
||||
gcc,
|
||||
gawk,
|
||||
git,
|
||||
grep,
|
||||
gzip,
|
||||
libarchive-tools,
|
||||
make,
|
||||
libc6-dev,
|
||||
python3 (>= 3.8)
|
||||
python3 (>= 3.8),
|
||||
sed,
|
||||
tar
|
||||
Standards-Version: 4.6.2
|
||||
Homepage: https://git.securityops.co/cristiancmoises/vaptvupt
|
||||
Vcs-Browser: https://git.securityops.co/cristiancmoises/vaptvupt
|
||||
Vcs-Git: https://git.securityops.co/cristiancmoises/vaptvupt.git
|
||||
Homepage: https://github.com/cristiancmoises/zupt
|
||||
Vcs-Browser: https://github.com/cristiancmoises/zupt
|
||||
Vcs-Git: https://github.com/cristiancmoises/zupt.git
|
||||
Rules-Requires-Root: no
|
||||
|
||||
Package: vaptvupt
|
||||
Package: zupt
|
||||
Architecture: any
|
||||
Provides: zupt (= ${binary:Version})
|
||||
Replaces: zupt
|
||||
Conflicts: zupt
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||
Description: Post-quantum backup compression utility (formerly Zupt)
|
||||
VaptVupt (renamed from Zupt in v3.0.0 due to INPI Brasil trademark) is
|
||||
a pure-C11 backup compression utility featuring:
|
||||
Description: Post-quantum backup compression utility
|
||||
ZUPT is a pure-C11 backup compression utility featuring:
|
||||
* Post-quantum hybrid encryption (ML-KEM-768 + X25519, FIPS 203)
|
||||
* AES-256-CTR + HMAC-SHA256 authenticated encryption (Encrypt-then-MAC)
|
||||
* 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
|
||||
* End-to-end byte-level tamper detection on encrypted archives
|
||||
(0 silent-accept positions in the v1.6 exhaustive byte sweep)
|
||||
* Constant-time cryptographic primitives verified with Jasmin
|
||||
* Authenticated encrypted-archive metadata and per-block integrity checks
|
||||
* Portable C implementations with optional source-built assembly paths
|
||||
* 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
|
||||
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
|
||||
header and footer, per-block HMAC with bound frame-preface AAD, and
|
||||
optional encrypted comments.
|
||||
Encrypted archives include an integrity trailer that authenticates the
|
||||
header and footer, per-block HMAC with bound frame-preface AAD, and optional
|
||||
encrypted comments. Plain archives use non-cryptographic checksums.
|
||||
|
|
|
|||
|
|
@ -1,19 +1,35 @@
|
|||
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>
|
||||
Source: https://git.securityops.co/cristiancmoises/vaptvupt
|
||||
Source: https://github.com/cristiancmoises/zupt
|
||||
|
||||
Files: *
|
||||
Copyright: 2025-2026 Cristian Cezar Moisés
|
||||
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)
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
Files: vendor/zuptsdk/*
|
||||
Copyright: 2025-2026 Cristian Cezar Moisés (libzuptsdk)
|
||||
License: GPL-3.0-or-later
|
||||
Files: src/zupt_xxh.c
|
||||
Copyright: 2012-2021 Yann Collet
|
||||
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/*
|
||||
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
|
||||
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
|
||||
|
||||
# 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-
|
||||
# specific ones.
|
||||
|
|
@ -15,21 +15,18 @@ export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
|
|||
|
||||
override_dh_auto_build:
|
||||
# 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:
|
||||
# Distro-safe regression subset (F-06..F-12, dedup-nonce, NIST/RFC vectors).
|
||||
$(MAKE) WITH_SDK=0 check
|
||||
# Distro-safe quick, traversal, integrity, codec and NIST/RFC checks.
|
||||
$(MAKE) WITH_SDK=0 WITH_PQBOX=0 check
|
||||
|
||||
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
|
||||
# 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:
|
||||
$(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
|
||||
;;; 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/
|
||||
;;; pthread from the store.
|
||||
;;;
|
||||
;;; 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 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 `vaptvupt` command in your profile too, also run:
|
||||
;;; guix package --install-from-expression='(begin (load "packaging/guix/vaptvupt.scm") vaptvupt)'
|
||||
;;; the `zupt` command in your profile too, also run:
|
||||
;;; 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
|
||||
;;; mesa, libxkbcommon, the X11/xcb family, libzstd, harfbuzz, icu, ...) that are
|
||||
|
|
@ -62,29 +62,30 @@
|
|||
xcb-util-renderutil xcb-util-wm xcb-util-cursor
|
||||
libinput-minimal mtdev libevdev eudev))
|
||||
|
||||
(define %vaptvupt-version "5.2.1")
|
||||
(define %zupt-version "5.2.2")
|
||||
|
||||
(define %vaptvupt-source
|
||||
(define %zupt-source
|
||||
(origin
|
||||
(method url-fetch)
|
||||
(uri (string-append
|
||||
"https://git.securityops.co/cristiancmoises/vaptvupt"
|
||||
"/releases/download/v" %vaptvupt-version
|
||||
"/vaptvupt-" %vaptvupt-version ".tar.gz"))
|
||||
"https://github.com/cristiancmoises/zupt"
|
||||
"/releases/download/v" %zupt-version
|
||||
"/zupt-" %zupt-version ".tar.gz"))
|
||||
(sha256
|
||||
(base32 "1mzl5za5k80x74p1hb9kfi199fs74ymmlcdhhxkzxr9ls8gpg6z2"))))
|
||||
(base32 "REPLACE_AFTER_FINAL_RELEASE_ARCHIVE_IS_BUILT"))))
|
||||
|
||||
(define-public vaptvupt
|
||||
(define-public zupt
|
||||
(package
|
||||
(name "vaptvupt")
|
||||
(version %vaptvupt-version)
|
||||
(source %vaptvupt-source)
|
||||
(name "zupt")
|
||||
(version %zupt-version)
|
||||
(source %zupt-source)
|
||||
(build-system gnu-build-system)
|
||||
(arguments
|
||||
(list
|
||||
#:make-flags
|
||||
#~(list (string-append "PREFIX=" #$output)
|
||||
"WITH_SDK=0"
|
||||
"WITH_PQBOX=0"
|
||||
(string-append "CC=" #$(cc-for-target)))
|
||||
#:phases
|
||||
#~(modify-phases %standard-phases
|
||||
|
|
@ -94,38 +95,49 @@
|
|||
;; SP 800-38A, RFC 4231/7748) are the crypto gate.
|
||||
(lambda* (#:key tests? #:allow-other-keys)
|
||||
(when tests?
|
||||
(invoke "make" "WITH_SDK=0"
|
||||
(invoke "make" "WITH_SDK=0" "WITH_PQBOX=0"
|
||||
(string-append "CC=" #$(cc-for-target))
|
||||
"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")
|
||||
(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
|
||||
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.
|
||||
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
|
||||
dispatch at runtime; the embedded VaptVupt 2.60.4 LZ+ANS codec ships
|
||||
CBMC-verified BCJ filters. Password mode uses PBKDF2-SHA256. The tool is
|
||||
AGPL-3.0-or-later; the embedded codec is GPL-3.0-or-later.")
|
||||
(license (list license:agpl3+ license:gpl3+))))
|
||||
random per-block nonce; AES-NI/SHA-NI dispatch at runtime; the bundled
|
||||
VaptVupt 2.65.3 LZ+ANS codec has portable fallbacks. Password mode uses
|
||||
PBKDF2-SHA256. The tool is
|
||||
AGPL-3.0-or-later; the embedded codec is GPL-3.0-or-later; the two
|
||||
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
|
||||
(name "vaptvupt-gui")
|
||||
(version %vaptvupt-version)
|
||||
(source (package-source vaptvupt)) ; same release tarball
|
||||
(name "zupt-gui")
|
||||
(version %zupt-version)
|
||||
(source (package-source zupt)) ; same release tarball
|
||||
(build-system copy-build-system)
|
||||
(arguments
|
||||
(list
|
||||
#:install-plan
|
||||
#~'(("gui/src/zupt_gui.py" "lib/vaptvupt-gui/")
|
||||
#~'(("gui/src/zupt_gui.py" "lib/zupt-gui/")
|
||||
("gui/assets/zupt-icon.png"
|
||||
"share/icons/hicolor/256x256/apps/vaptvupt-gui.png")
|
||||
("gui/README.md" "share/doc/vaptvupt-gui/")
|
||||
("gui/LICENSE-GUI" "share/doc/vaptvupt-gui/"))
|
||||
"share/icons/hicolor/256x256/apps/zupt-gui.png")
|
||||
("gui/README.md" "share/doc/zupt-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
|
||||
#~(modify-phases %standard-phases
|
||||
(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"))
|
||||
(bin (string-append out "/bin"))
|
||||
(gui (string-append
|
||||
out "/lib/vaptvupt-gui/zupt_gui.py"))
|
||||
out "/lib/zupt-gui/zupt_gui.py"))
|
||||
(sh (search-input-file inputs "/bin/sh"))
|
||||
(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"))
|
||||
(site (car (find-files pyside "^site-packages$"
|
||||
#: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")))
|
||||
":")))
|
||||
(mkdir-p bin)
|
||||
(call-with-output-file (string-append bin "/vaptvupt-gui")
|
||||
(call-with-output-file (string-append bin "/zupt-gui")
|
||||
(lambda (port)
|
||||
(format port "#!~a
|
||||
export VAPTVUPT_BIN=\"~a\"
|
||||
export ZUPT_BIN=\"~a\"
|
||||
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 LD_LIBRARY_PATH=\"~a${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH\"
|
||||
exec \"~a\" \"~a\" \"$@\"\n"
|
||||
sh cli site shsite qtbase qtwl ldpath python3 gui)))
|
||||
(chmod (string-append bin "/vaptvupt-gui") #o755)
|
||||
(symlink "vaptvupt-gui" (string-append bin "/zupt-gui")))))
|
||||
(chmod (string-append bin "/zupt-gui") #o755))))
|
||||
(add-after 'make-launcher 'install-desktop-file
|
||||
(lambda* (#:key outputs #:allow-other-keys)
|
||||
(let* ((out (assoc-ref outputs "out"))
|
||||
(apps (string-append out "/share/applications")))
|
||||
(mkdir-p apps)
|
||||
(call-with-output-file
|
||||
(string-append apps "/vaptvupt-gui.desktop")
|
||||
(string-append apps "/zupt-gui.desktop")
|
||||
(lambda (port)
|
||||
(format port "[Desktop Entry]
|
||||
Type=Application
|
||||
Name=VaptVupt
|
||||
Name=ZUPT
|
||||
GenericName=Post-Quantum Backup
|
||||
Comment=Compress, encrypt and restore .zupt archives
|
||||
Exec=~a/bin/vaptvupt-gui %F
|
||||
Icon=vaptvupt-gui
|
||||
Exec=~a/bin/zupt-gui %F
|
||||
Icon=zupt-gui
|
||||
Terminal=false
|
||||
Categories=Utility;Archiving;Security;
|
||||
MimeType=application/x-zupt;
|
||||
|
|
@ -189,20 +200,20 @@ Keywords=backup;encryption;post-quantum;compression;zupt;\n"
|
|||
out)))))))))
|
||||
(inputs
|
||||
(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
|
||||
%gui-runtime-libs))
|
||||
(home-page "https://git.securityops.co/cristiancmoises/vaptvupt")
|
||||
(synopsis "Desktop frontend for the VaptVupt post-quantum backup tool")
|
||||
(home-page "https://github.com/cristiancmoises/zupt")
|
||||
(synopsis "Desktop frontend for the ZUPT post-quantum backup tool")
|
||||
(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
|
||||
encryption, including the @code{--pq} hybrid and @code{--pq-only} full
|
||||
post-quantum modes. The launcher pins the matching @code{vaptvupt} CLI from the
|
||||
store via @env{VAPTVUPT_BIN} and sets @env{LD_LIBRARY_PATH} to the Qt6 leaf
|
||||
post-quantum modes. The launcher pins the matching @code{zupt} CLI from the
|
||||
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.")
|
||||
(license license:agpl3+)))
|
||||
|
||||
;; `guix package -f' evaluates the file's last expression — the GUI, which
|
||||
;; carries the CLI as an input.
|
||||
vaptvupt-gui
|
||||
zupt-gui
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# Homebrew formula for zupt.
|
||||
# Homebrew formula for ZUPT.
|
||||
#
|
||||
# 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.
|
||||
# 3. Update `url`, `version`, and `sha256` below.
|
||||
# 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.
|
||||
# * 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)"
|
||||
homepage "https://git.securityops.co/cristiancmoises/vaptvupt"
|
||||
url "https://git.securityops.co/cristiancmoises/vaptvupt/releases/download/v5.2.1/vaptvupt-5.2.1.tar.gz"
|
||||
version "5.2.1"
|
||||
homepage "https://github.com/cristiancmoises/zupt"
|
||||
url "https://github.com/cristiancmoises/zupt/releases/download/v5.2.2/zupt-5.2.2.tar.gz"
|
||||
version "5.2.2"
|
||||
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
|
||||
|
||||
|
|
@ -35,18 +35,24 @@ class Vaptvupt < Formula
|
|||
# this and falls back cleanly.
|
||||
ENV["CFLAGS"] = "#{ENV.cflags} -O2 -std=c11 -Wall -Wextra"
|
||||
|
||||
system "make", "WITH_SDK=0", "-j#{ENV.make_jobs}"
|
||||
system "make", "DESTDIR=#{prefix}", "PREFIX=", "WITH_SDK=0", "install"
|
||||
system "make", "WITH_SDK=0", "WITH_PQBOX=0", "-j#{ENV.make_jobs}"
|
||||
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"
|
||||
%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
|
||||
|
||||
test do
|
||||
# End-to-end sanity check: build a real archive, extract it, byte-compare.
|
||||
(testpath/"input.txt").write("homebrew formula test payload\n")
|
||||
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"
|
||||
cd "extracted" do
|
||||
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
|
||||
# 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
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ZUPT_CLI_DEB="$SCRIPT_DIR/zupt_2.2.3_amd64.deb"
|
||||
ZUPT_GUI_DEB="$SCRIPT_DIR/zupt-gui_1.1.1_all.deb"
|
||||
|
||||
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"
|
||||
# Stable entry point for the source installer. Dependency installation belongs
|
||||
# to the operating-system package manager; this script performs no downloads.
|
||||
set -Eeuo pipefail
|
||||
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)
|
||||
exec "$repo_root/gui/install.sh" "$@"
|
||||
|
|
|
|||
|
|
@ -1,25 +1,22 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# Nix flake for zupt.
|
||||
# Nix flake for ZUPT.
|
||||
#
|
||||
# Usage (with flakes enabled):
|
||||
# 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 flake check # lint the 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;
|
||||
#
|
||||
# Reproducibility:
|
||||
# * Nix already pins the source tree by hash.
|
||||
# * `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.
|
||||
# `make dist` has its own reproducibility gate. This development flake has no
|
||||
# committed lock file and therefore makes no independent locked-output claim.
|
||||
|
||||
{
|
||||
description = "Zupt — post-quantum backup compression utility (C11)";
|
||||
description = "ZUPT — post-quantum backup compression utility (C11)";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
||||
|
|
@ -27,22 +24,25 @@
|
|||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system:
|
||||
flake-utils.lib.eachSystem [ "x86_64-linux" ] (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
|
||||
zupt = pkgs.stdenv.mkDerivation {
|
||||
pname = "vaptvupt";
|
||||
version = "5.0.0";
|
||||
pname = "zupt";
|
||||
version = "5.2.2";
|
||||
|
||||
# When publishing, replace this with `fetchurl` against the
|
||||
# release tarball. For local development the flake assumes it
|
||||
# lives in the same directory as the source.
|
||||
src = ./.;
|
||||
src = builtins.path { path = ../..; name = "zupt-source"; };
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
gcc
|
||||
git
|
||||
gnumake
|
||||
file
|
||||
gnutar
|
||||
];
|
||||
|
||||
# python3 is only used by the regression-test harness.
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
# Source-only build (WITH_SDK=0): native crypto, no vendored libraries.
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
make WITH_SDK=0 -j$NIX_BUILD_CORES
|
||||
make WITH_SDK=0 WITH_PQBOX=0 -j$NIX_BUILD_CORES
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
|
|
@ -63,27 +63,30 @@
|
|||
doCheck = true;
|
||||
checkPhase = ''
|
||||
runHook preCheck
|
||||
make WITH_SDK=0 check
|
||||
make WITH_SDK=0 WITH_PQBOX=0 check
|
||||
runHook postCheck
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
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
|
||||
mkdir -p $out/share/doc/vaptvupt
|
||||
cp README.md SECURITY.md CHANGELOG.md $out/share/doc/vaptvupt/
|
||||
mkdir -p $out/share/doc/zupt
|
||||
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
|
||||
'';
|
||||
|
||||
meta = with pkgs.lib; {
|
||||
description = "Post-quantum backup compression utility (ML-KEM-768 + X25519 + AES-256-CTR + HMAC-SHA256)";
|
||||
homepage = "https://git.securityops.co/cristiancmoises/vaptvupt";
|
||||
license = with licenses; [ agpl3Plus gpl3Plus ];
|
||||
homepage = "https://github.com/cristiancmoises/zupt";
|
||||
license = with licenses; [ agpl3Plus gpl3Plus bsd2 bsd3 cc0 ];
|
||||
maintainers = [ ];
|
||||
platforms = [ "x86_64-linux" "aarch64-linux" ];
|
||||
mainProgram = "vaptvupt";
|
||||
platforms = [ "x86_64-linux" ];
|
||||
mainProgram = "zupt";
|
||||
};
|
||||
};
|
||||
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`
|
||||
in OBS:
|
||||
This directory is the upstream, source-only OBS recipe for ZUPT. It is a
|
||||
handoff for the downstream maintainer; its presence does not mean that the
|
||||
package has been submitted to or accepted by openSUSE Factory.
|
||||
|
||||
| File | Purpose |
|
||||
|---------------|-------------------------------------------------------------------------|
|
||||
| `_service` | `revision` pinned to `v5.0.0`. Format unchanged (still `tar_scm`). |
|
||||
| `vaptvupt.spec` | `Version: 5.0.0`. `License: AGPL-3.0-or-later`. `%check` calls `make check`. |
|
||||
| `vaptvupt.changes`| Changelog for the 4.x series. Older history preserved verbatim. |
|
||||
Cristian Cezar Moisés, ZUPT's creator and current upstream maintainer,
|
||||
prepared the 5.2.2 source, build, test, documentation, and upstream packaging
|
||||
changes in this handoff. Alessandro de Oliveira Faria (Cabelo) is credited only
|
||||
as the openSUSE collaborator and downstream OBS package maintainer: he reviews
|
||||
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
|
||||
+ commercial).
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `_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
|
||||
only `gcc gzip make` (plus `libm`/`pthread` from glibc). There are
|
||||
**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 source service uses `obs_scm`, with Git submodules and Git LFS explicitly
|
||||
disabled. Its primary URL is the canonical upstream:
|
||||
|
||||
The optional SDK modes (`--pq-sdk`, `--pq-box`) and the Argon2id KDF
|
||||
require an upstream `make WITH_SDK=1` build linked against the
|
||||
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"
|
||||
```text
|
||||
https://github.com/cristiancmoises/zupt.git
|
||||
```
|
||||
|
||||
## 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
|
||||
release, edit that one line and re-run `osc service runall`.
|
||||
* The spec's `Version:` field is hard-coded — when you bump `_service`
|
||||
`revision`, also bump `Version:` to match.
|
||||
* `BuildRequires` is intentionally minimal (`gcc gzip make`). vaptvupt
|
||||
has no external library dependencies in the default build; do not add
|
||||
system crypto BuildRequires.
|
||||
This source policy does not prohibit separately built release-page packages.
|
||||
The upstream 5.2.2 gates may publish the CLI source tarball, DEB, binary RPM,
|
||||
SRPM, notice-bearing Linux tar.xz, Windows ZIP, and macOS DMG, together with a
|
||||
GUI DEB, noarch RPM, GUI SRPM, and source-only portable GUI ZIP after each
|
||||
format-specific test succeeds. None of those files is an OBS `Source0` input
|
||||
or belongs in Git. AppImage and bare executables remain 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.
|
||||
|
||||
## Reporting issues
|
||||
## License and bundled codec
|
||||
|
||||
* Upstream bugs: https://git.securityops.co/cristiancmoises/vaptvupt
|
||||
* openSUSE packaging bugs: https://bugs.opensuse.org/
|
||||
* Cabelo's OBS project: https://build.opensuse.org/project/show/home:cabelo:innovators
|
||||
The resulting executable combines the AGPL-3.0-or-later application with the
|
||||
GPL-3.0-or-later VaptVupt codec, adapted BSD-2-Clause XXH64 routines, and
|
||||
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>
|
||||
<service name="tar_scm" mode="manual">
|
||||
<param name="url">https://github.com/cristiancmoises/vaptvupt</param>
|
||||
<param name="scm">git</param>
|
||||
<param name="revision">v5.0.0</param>
|
||||
<param name="versionformat">@PARENT_TAG@</param>
|
||||
<param name="versionrewrite-pattern">v(.*)</param>
|
||||
<param name="submodules">enable</param>
|
||||
<param name="filename">vaptvupt</param>
|
||||
</service>
|
||||
<service name="recompress" mode="manual">
|
||||
<param name="file">*.tar</param>
|
||||
<param name="compression">gz</param>
|
||||
</service>
|
||||
<service name="set_version" mode="manual"/>
|
||||
<service name="obs_scm" mode="manual">
|
||||
<param name="url">https://github.com/cristiancmoises/zupt.git</param>
|
||||
<param name="scm">git</param>
|
||||
<param name="revision">refs/tags/v5.2.2</param>
|
||||
<param name="versionformat">@PARENT_TAG@</param>
|
||||
<param name="versionrewrite-pattern">^v(.*)$</param>
|
||||
<param name="versionrewrite-replacement">\1</param>
|
||||
<param name="filename">zupt</param>
|
||||
<param name="submodules">disable</param>
|
||||
<param name="lfs">disable</param>
|
||||
</service>
|
||||
<service name="tar" mode="buildtime"/>
|
||||
<service name="recompress" mode="buildtime">
|
||||
<param name="file">*.tar</param>
|
||||
<param name="compression">gz</param>
|
||||
</service>
|
||||
</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>
|
||||
|
||||
|
|
@ -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>
|
||||
|
||||
- Update to 3.0.1
|
||||
* GUI license cleanup: removed MIT credit line from the about
|
||||
panel; gui/LICENSE-GUI replaced (was MIT) with AGPL-3.0-or-later
|
||||
to match the source SPDX header. The GUI was never actually
|
||||
released under MIT — that was a templating mistake.
|
||||
* GUI license metadata changed to AGPL-3.0-or-later for the then-current
|
||||
source. The original entry incorrectly denied earlier MIT grants; the
|
||||
5.2.2 erratum records that they remain valid for the exact historical
|
||||
material distributed under them.
|
||||
* GUI version-string parsing bug fix (the replace("zupt ", ...)
|
||||
substring also matched inside the v3.0.0 parenthetical). Window
|
||||
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
|
||||
tests). Spec now calls `make check` on x86_64/aarch64.
|
||||
* 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.
|
||||
|
||||
-------------------------------------------------------------------
|
||||
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
|
||||
(PySide6, or PyQt6 as a fallback). It runs on Windows, macOS, Linux and the
|
||||
BSDs — anywhere Python 3 and a Qt binding are installed. This portable package
|
||||
contains the GUI plus a launcher for each platform; it drives the `vaptvupt`
|
||||
command-line tool under the hood.
|
||||
This tracked directory contains three launcher templates and this assembly
|
||||
guide; it is not a complete bundle by itself. A downstream source-only bundle
|
||||
may add the integrated Python GUI source and artwork listed below, together
|
||||
with the required license/provenance files. It must not contain Python, Qt, a
|
||||
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
|
||||
--------
|
||||
zupt_gui.py The GUI (PySide6 / PyQt6).
|
||||
vaptvupt-gui.bat Windows launcher.
|
||||
vaptvupt-gui.command macOS launcher (double-clickable in Finder).
|
||||
vaptvupt-gui.sh Linux / BSD launcher.
|
||||
assets/zupt-icon.png Application icon.
|
||||
zupt_gui.py GUI source module (the historical module filename is
|
||||
retained internally for source compatibility).
|
||||
zupt-gui.bat Windows launcher.
|
||||
zupt-gui.command macOS Finder launcher.
|
||||
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
|
||||
------------
|
||||
1. Python 3.8 or newer.
|
||||
Windows: https://python.org (tick "Add python.exe to PATH")
|
||||
macOS: python.org, or `brew install python`
|
||||
Linux: your distro's python3 package
|
||||
FreeBSD: pkg install python311
|
||||
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).
|
||||
1. Python 3.9 or newer.
|
||||
2. PySide6 6.5 or newer, or a compatible PyQt6 package.
|
||||
3. ZUPT 5.2.2, installed as `zupt` on PATH or placed beside the launcher
|
||||
(`zupt.exe` on Windows). A local command must have been built
|
||||
and tested independently; this bundle never downloads one.
|
||||
|
||||
Running
|
||||
-------
|
||||
Windows: double-click vaptvupt-gui.bat
|
||||
macOS: double-click vaptvupt-gui.command
|
||||
(first run: right-click > Open to bypass Gatekeeper for an
|
||||
unsigned script, or `xattr -dr com.apple.quarantine .`)
|
||||
Linux/BSD: ./vaptvupt-gui.sh
|
||||
Windows: zupt-gui.bat
|
||||
macOS: zupt-gui.command
|
||||
POSIX: ./zupt-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
|
||||
---------------
|
||||
* "requires PySide6 or PyQt6" -> install a Qt binding (requirement 2).
|
||||
* "vaptvupt not found" -> put the CLI next to the launcher or on PATH.
|
||||
* Set VAPTVUPT_DEBUG=1 to print the binary-discovery log to stderr.
|
||||
* "requires PySide6 or PyQt6": install one Qt binding through your operating
|
||||
system package manager or another trusted, preconfigured Python source.
|
||||
* "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
|
||||
bundle Python + Qt + the CLI are produced by the project's CI on real Windows
|
||||
and macOS runners — see the release page. This portable package is the
|
||||
dependency-light option that works identically on every platform.
|
||||
The old user-facing command name is not installed by this bundle. The `.zupt`
|
||||
archive extension remains unchanged for format compatibility.
|
||||
|
||||
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
|
||||
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 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 * 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 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.
|
||||
setlocal
|
||||
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.
|
||||
where py >nul 2>nul
|
||||
|
|
@ -23,9 +23,8 @@ if %ERRORLEVEL%==0 (
|
|||
set "RC=%ERRORLEVEL%"
|
||||
if not "%RC%"=="0" (
|
||||
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 the CLI was not found, put vaptvupt.exe next to this launcher or on PATH.
|
||||
pause
|
||||
echo If the CLI was not found, put zupt.exe next to this launcher or on PATH.
|
||||
)
|
||||
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
|
||||
# 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:
|
||||
# * Python 3.8+
|
||||
# * Python 3.9+
|
||||
# * PySide6 or PyQt6
|
||||
# Debian/Ubuntu: sudo apt install python3-pyqt6
|
||||
# Fedora/RHEL: sudo dnf install python3-pyqt6
|
||||
# FreeBSD: pkg install py311-pyside6 (or py311-qt6-pyqt)
|
||||
# OpenBSD: pkg_add py3-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)"
|
||||
[ -x "$HERE/vaptvupt" ] && export VAPTVUPT_BIN="$HERE/vaptvupt"
|
||||
[ -x "$HERE/zupt" ] && export ZUPT_BIN="$HERE/zupt"
|
||||
|
||||
PY="$(command -v python3 || command -v python || true)"
|
||||
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
|
||||
fi
|
||||
exec "$PY" "$HERE/zupt_gui.py" "$@"
|
||||
|
|
@ -1,72 +1,70 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# Fedora / RHEL / CentOS RPM spec for vaptvupt.
|
||||
# Fedora / RHEL / CentOS RPM spec for zupt.
|
||||
#
|
||||
# Build with:
|
||||
# spectool -g vaptvupt.spec # fetches the upstream tarball
|
||||
# rpmbuild -ba vaptvupt.spec # builds source + binary RPMs
|
||||
# spectool -g zupt.spec # fetches the upstream tarball
|
||||
# rpmbuild -ba zupt.spec # builds source + binary RPMs
|
||||
#
|
||||
# To bring a release into production:
|
||||
# 1. Run `make dist` upstream → /tmp/vaptvupt-VERSION.tar.gz (reproducible).
|
||||
# 2. Upload to a stable release URL (git.securityops.co releases).
|
||||
# 1. Run `make dist` upstream → /tmp/zupt-VERSION.tar.gz (reproducible).
|
||||
# 2. Upload to the canonical GitHub release.
|
||||
# 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
|
||||
# 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
|
||||
# on RHEL 8 (with EPEL) by adjusting BuildRequires if Python 3.8+ isn't
|
||||
# in the base.
|
||||
# This is an upstream Fedora-family recipe. A target is supported only after
|
||||
# that exact distribution release and architecture have built and passed the
|
||||
# installed smoke test.
|
||||
|
||||
Name: vaptvupt
|
||||
Version: 5.2.1
|
||||
Name: zupt
|
||||
Version: 5.2.2
|
||||
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
|
||||
URL: https://git.securityops.co/cristiancmoises/vaptvupt
|
||||
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: %{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: git-core
|
||||
BuildRequires: make
|
||||
BuildRequires: glibc-devel
|
||||
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,
|
||||
# tamper injection). The shipped binary has no Python dependency.
|
||||
|
||||
Requires: glibc
|
||||
Provides: bundled(vaptvupt-codec) = 2.65.3
|
||||
|
||||
%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,
|
||||
validated byte-for-byte against OpenSSL's ML-KEM-768) and full
|
||||
pure ML-KEM-768 (--pq-only)
|
||||
* Post-quantum hybrid encryption (ML-KEM-768 + X25519) and full
|
||||
ML-KEM-768 mode (--pq-only)
|
||||
* AES-256-CTR + HMAC-SHA256 authenticated encryption (Encrypt-then-MAC)
|
||||
* PBKDF2-SHA256 password key derivation (Argon2id in WITH_SDK=1 builds)
|
||||
* Multi-threaded compression with the VaptVupt LZ + ANS codec
|
||||
* Full-disk backup and restore with sparse-region detection
|
||||
* End-to-end byte-level tamper detection on encrypted archives
|
||||
(0 silent-accept positions in the v1.6 exhaustive byte sweep)
|
||||
* Constant-time cryptographic primitives verified with Jasmin
|
||||
* Authenticated encrypted-archive metadata and per-block integrity checks
|
||||
* Portable C implementations with optional source-built assembly paths
|
||||
* 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
|
||||
the header and footer, per-block HMAC with bound frame-preface AAD,
|
||||
and optional encrypted comments.
|
||||
|
||||
%global debug_package %{nil}
|
||||
# Single source RPM, no -debuginfo split for the initial release.
|
||||
Encrypted archives include an integrity trailer that authenticates the header
|
||||
and footer, per-block HMAC with bound frame-preface AAD, and optional encrypted
|
||||
comments. Plain archives use non-cryptographic checksums.
|
||||
|
||||
%prep
|
||||
%autosetup -n %{name}-%{version}
|
||||
|
|
@ -74,41 +72,41 @@ and optional encrypted comments.
|
|||
%build
|
||||
# Source-only build (WITH_SDK=0): no vendored libraries, no external crypto
|
||||
# dependency. Fedora's default optflags plus the project's warning set.
|
||||
%make_build WITH_SDK=0 \
|
||||
CFLAGS="%{optflags} -fPIE -Wall -Wextra -std=c11 -Iinclude -Isrc" \
|
||||
LDFLAGS="%{?build_ldflags} -pie" \
|
||||
LDLIBS="-lm -lpthread"
|
||||
%make_build V=1 WITH_SDK=0 WITH_PQBOX=0 \
|
||||
CFLAGS="%{optflags}" \
|
||||
LDFLAGS="%{?build_ldflags}"
|
||||
|
||||
%check
|
||||
# Distro-safe regression subset: F-06 HMAC trials, F-08 top-MAC sweep,
|
||||
# F-09 byte sweep, F-10..F-12 regressions, the dedup-nonce regression,
|
||||
# and NIST/RFC vectors. Skips threaded/dist-reproducibility tests that
|
||||
# are sensitive to the build host.
|
||||
%make_build WITH_SDK=0 \
|
||||
CFLAGS="%{optflags} -fPIE -Wall -Wextra -std=c11 -Iinclude -Isrc" \
|
||||
LDFLAGS="%{?build_ldflags} -pie" \
|
||||
LDLIBS="-lm -lpthread" \
|
||||
# Distro-safe quick, path-traversal, integrity, codec, HMAC and NIST/RFC
|
||||
# checks. Full, optional-integration and dist-reproducibility suites remain
|
||||
# release gates outside the package build.
|
||||
%make_build V=1 WITH_SDK=0 WITH_PQBOX=0 \
|
||||
CFLAGS="%{optflags}" \
|
||||
LDFLAGS="%{?build_ldflags}" \
|
||||
check
|
||||
|
||||
%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
|
||||
%license LICENSE
|
||||
%doc README.md SECURITY.md CHANGELOG.md
|
||||
%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 THREAT_MODEL.md CHANGELOG.md
|
||||
%{_bindir}/%{name}
|
||||
%{_bindir}/zupt
|
||||
%{_datadir}/bash-completion/completions/%{name}
|
||||
%{_datadir}/bash-completion/completions/zupt
|
||||
%{_datadir}/zsh/site-functions/_%{name}
|
||||
%{_datadir}/zsh/site-functions/_zupt
|
||||
%{_datadir}/fish/vendor_completions.d/%{name}.fish
|
||||
%if 0%{?_mandir:1}
|
||||
%{_mandir}/man1/%{name}.1*
|
||||
%{_mandir}/man1/zupt.1*
|
||||
%endif
|
||||
|
||||
%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
|
||||
- Codec 2.65.0; large compression-ratio gains (auto format_v2 + level-scaled
|
||||
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
|
||||
Loading…
Reference in a new issue